diff --git a/.dtop.yml b/.dtop.yml new file mode 100644 index 0000000..b19b277 --- /dev/null +++ b/.dtop.yml @@ -0,0 +1,3 @@ +hosts: + - host: local + dozzle: https://logs.localhost/ diff --git a/.github/workflows/pg-backup.yml b/.github/workflows/backup.yml similarity index 95% rename from .github/workflows/pg-backup.yml rename to .github/workflows/backup.yml index 1eb8381..a152bbc 100644 --- a/.github/workflows/pg-backup.yml +++ b/.github/workflows/backup.yml @@ -1,4 +1,4 @@ -name: PostgreSQL Backup +name: Backup on: schedule: - cron: '0 0 * * *' # Sync daily at midnight @@ -9,7 +9,7 @@ concurrency: jobs: pg_dump: if: vars.SSH_HOSTNAME != '' - name: Capture and store backup + name: PostgreSQL runs-on: ubuntu-latest environment: name: production diff --git a/bin/pg_backup.sh b/bin/backup_capture.sh similarity index 100% rename from bin/pg_backup.sh rename to bin/backup_capture.sh diff --git a/bin/backup_download.sh b/bin/backup_download.sh new file mode 100755 index 0000000..b6397a1 --- /dev/null +++ b/bin/backup_download.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +# Download the database dump from the latest GitHub workflow +# Usage: ./backup_download.sh [workflow_id] + +set -eux + +workflow_id="${1:-$(gh api "repos/{owner}/{repo}/actions/runs" | jq -r '.workflow_runs[] | select(.name == "Backup" and .conclusion == "success") | .id' | sed 's/"//g' | head -n 1)}" + +gh run download "$workflow_id" -n backup.dump diff --git a/bin/backup_restore.sh b/bin/backup_restore.sh new file mode 100755 index 0000000..e65e623 --- /dev/null +++ b/bin/backup_restore.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh + +# Restore the PostgreSQL database from a dump file +# Usage: ./backup_restore.sh [dump_file] [database_name] [num_jobs] + +set -eux + +dump_file="${1:-backup.dump}" +database_name="${2:-postgres}" +num_jobs="${3:-$(getconf _NPROCESSORS_ONLN)}" + +pg_restore "$dump_file" -d "$database_name" --no-acl --no-owner --no-privileges -j "$num_jobs" --disable-triggers diff --git a/compose.yml b/compose.yml index bbe3d75..01e1ec6 100644 --- a/compose.yml +++ b/compose.yml @@ -1,5 +1,6 @@ include: - path: + - containers/web/compose.yml - containers/web/python/compose.yml - containers/compose.smtp.yml - containers/compose.postgres.yml diff --git a/containers/web/compose.yml b/containers/web/compose.yml new file mode 100644 index 0000000..06ac206 --- /dev/null +++ b/containers/web/compose.yml @@ -0,0 +1,10 @@ +services: + web: + memswap_limit: 1g # Limit total memory usage (RAM + swap) to 1GB + deploy: + mode: replicated + replicas: 2 + resources: + limits: + cpus: '0.25' # Limit to 25% of a CPU + memory: 512M # Limit to 512MB of RAM diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8479051 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +# freePaaS + +freePaaS is an open-source platform-as-a-service (PaaS) solution that enables developers to easily deploy, manage, and scale applications in a cloud environment. It provides a user-friendly interface and a variety of tools to streamline the application lifecycle, from development to production. + +## Features + +- [Easy Deployment](deployment.md): Deploy applications with just a few clicks. +- [Scalability](scaling.md): Automatically scale applications based on demand. +- [Monitoring](monitoring.md): Keep track of application performance and health. +- [Environment Management](enviroment.md): Manage configuration and environment variables effectively. +- [Backups](backups.md): Schedule and manage database backups with ease. +- [File Storage](storage.md): Handle file uploads and storage seamlessly. + +## Why and How + +freePaaS aims to provide a zero touch CD pipeline for developers, allowing them to focus on writing code rather than managing infrastructure. + +It is built around best practices to provide simplicity and security, while providing you the freedom to customize anything you want. + +freePaaS uses technologies most developers are already familiar with to lower the learning curve and speed up adoption. +No PhD in Kubernetes is required to deploy and manage applications, since we don't use it. diff --git a/docs/backups.md b/docs/backups.md new file mode 100644 index 0000000..afe2925 --- /dev/null +++ b/docs/backups.md @@ -0,0 +1,34 @@ +# Backups + +## Database backups + +PostgreSQL backups are captured daily using [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and stored as repository artifacts in GitHub Actions. + +### Durability + +By default, GitHub retains workflow artifacts for 90 days. You can [adjust the retention period](https://docs.github.com/en/organizations/managing-organization-settings/configuring-the-retention-period-for-github-actions-artifacts-and-logs-in-your-organization) up to a maximum of 400 days. + +Importantly, artifacts are stored independently of your application server, ensuring that backups remain safe even if your server fails. + +The backup frequency may be altered in the [`.github/workflows/backup.yml`](../.github/workflows/backup.yml) file. Here you may also configure additional backup targets, such as cloud storage providers. + +### Restoration + +To restore a backup, download the desired artifact from the GitHub Actions workflow run history. The artifact will be a compressed file containing the SQL dump. + +You can restore the database using the built-in database scripts: + +```bash +bin/backup_download.sh +bin/backup_restore.sh +``` + +> [!NOTE] +> Backups are stored in PostgreSQL's [custom format](https://www.postgresql.org/docs/current/app-pgdump.html) which is compressed and allows for more flexible restoration options. + +### Privacy + +With backups being stored on GitHub, it's crucial to consider the sensitivity of your data. Ensure that your repository is private to prevent unauthorized access to your backups. Additionally, consider encrypting your database dumps before uploading them as artifacts for an added layer of security. + +> [!IMPORTANT] +> If you are serving customers in the EU, ensure that you add GitHub as a data processor in your privacy policy to comply with GDPR regulations. diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..17c0d89 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,34 @@ +# Deployment + +Deploying your application with freePaaS is a streamlined process that involves running an installation script and leveraging GitHub Actions for continuous deployment. + +## Initial Setup + +The primary setup is handled by an interactive script. To start the installation wizard, run the following command from the root of the repository: + +```bash +./bin/install.sh +``` + +This script will guide you through the following steps: + +1. **Domain Configuration**: You will be prompted to enter your domain name. +1. **Server Access**: The script will verify SSH access to your server. +1. **GitHub Integration**: It will help you create a GitHub OAuth App for secure authentication. +1. **Repository Configuration**: The script will set up the necessary GitHub repository secrets and variables to enable automated deployments. These include: + - `SSH_HOSTNAME`: Your server's hostname or IP address. + - `SSH_PRIVATE_KEY`: A private SSH key for accessing the server. + - `SSH_KNOWN_HOSTS`: Your server's SSH host key. + +## Continuous Deployment + +Once the initial setup is complete, your application will be automatically deployed whenever you push changes to the `main` branch. This is handled by the [`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml) GitHub Actions workflow. + +The deployment workflow performs the following steps: + +1. **Trigger**: The workflow is triggered by a push to the `main` branch (after the `ci` workflow succeeds) or can be triggered manually. +1. **Environment Setup**: It sets up an SSH connection to your production server using the configured secrets. +1. **Remote Deployment**: It establishes a remote Docker context to your server. +1. **Application Start**: It uses `docker compose` to pull the latest images and start the application containers. + +Your application will be served via a Caddy reverse proxy, which also handles automatic SSL certificate provisioning. diff --git a/docs/enviroment.md b/docs/enviroment.md new file mode 100644 index 0000000..82e7bfc --- /dev/null +++ b/docs/enviroment.md @@ -0,0 +1,49 @@ +# Environment + +[12-factor] apps are designed to be portable and resilient by strictly separating configuration from code. This approach allows applications to adapt seamlessly across different environments, such as development, staging, and production. + +Your environment variables are stored on GitHub in your repository. + +## Default runtime variables + +The default variables are set: + +- `HOSTNAME`: The hostname of your application. +- `DATABASE_URL`: The URL for your database connection. +- `REDIS_URL`: The URL for your Redis instance. +- `EMAIL_URL`: The URL to your SMTP relay instance. + +## Managing variables + +GitHub can store multiple environments for a single repository. Each environment can have its own set of variables and secrets. You can create environments such as `development`, `staging`, and `production` to manage different configurations for each stage of your application lifecycle. + +If your workflow targets a specific environment, GitHub Actions will automatically load the corresponding variables and secrets for that environment during the workflow run. + +> [!IMPORTANT] +> GitHub will inherit secrets from the repository level to the environment level, but not the other way around. Be cautious when naming secrets at both levels to avoid unintentional overrides. Environment-level secrets will take precedence over repository-level secrets with the same name. + +### Variables + +Variables are stored in plain text and retrievable. + +```bash +# With body +gh variable set VARIABLE_NAME --env production --body "variable_value" +# from file +gh variable set VARIABLE_NAME --env production ` + +To access via shell, use the following commands: + +```bash +dtop +``` + +The bootstrap script creates a `.dtop.yml` configuration file for your project with production and development contexts. + +## Application Monitoring + +freePaaS provides only basic monitoring tools out of the box to help you assess your container health. For more advanced monitoring, logging, and alerting capabilities, consider integrating third-party services such as [Sentry]. + +[dozzle]: https://dozzle.dev/ +[dtop]: https://dtop.dev/ +[sentry]: https://sentry.io/welcome/ diff --git a/docs/scaling.md b/docs/scaling.md new file mode 100644 index 0000000..5f8c6cf --- /dev/null +++ b/docs/scaling.md @@ -0,0 +1,60 @@ +# Scaling + +freePaaS allows you to start small and scale your applications as they grow in popularity. There are multiple ways to progressively scale your applications depending on your needs without breaking the bank. + +## Scaling services + +freePaaS defaults to a highly available web server setup with a minimum of two web servers behind a load balancer. This allows your application to handle more traffic and provides redundancy in case one of the web servers goes down. + +You can easily add more web servers to your application by simply adding more containers to the `web` service in your `compose.yml` file. + +```yaml +services: + web: + deploy: + replicas: 3 # Increase the number of replicas +``` + +You may also scale ad-hoc using the Docker CLI: + +```bash +docker service scale web=5 # Scale to 5 replicas +``` + +### Resource management + +You can also limit the resources used by each service in your [`containers/web/compose.yml`](../containers/web/compose.yml) file. This allows you to control the amount of CPU and memory used by each service. + +```yaml +services: + web: + memswap_limit: 1g # Limit total memory + swap to 1GB + deploy: + resources: + limits: + cpus: '0.50' # Limit to 50% of a CPU + memory: 512M # Limit to 512MB of RAM + reservations: + cpus: '0.25' # Reserve 25% of a CPU + memory: 256M # Reserve 256MB of RAM +``` + +> [!IMPORTANT] +> Setting resource limits is important to prevent a single service from consuming all available resources on the server, which could lead to performance degradation or crashes. You MUST always set `memswap_limit` to prevent services from using swap space. Swapping will prevent OOM (out of memory) restarts. + +## Long term growth strategies + +### Scaling Vertically + +Most datacenters will offer VPS or dedicated servers in a variety of sizes and the ability to upgrade an existing server to a larger size. This is known as vertical scaling or scaling up. + +This will probably be the easiest way to scale your application, especially if you are just starting out. Simply upgrade your server to a larger size and freePaaS will automatically take advantage of the additional resources. + +### Scaling Horizontally + +When your application outgrows the resources of a single server, you can just add a second one. Docker [Swarm mode](https://docs.docker.com/engine/swarm/) is low effort way to just add more servers to your PaaS. Setup takes minutes and freePaaS will automatically distribute your applications across the available servers. + +### Hyperscale + +Congratulations, you made it! Your application is so popular that you need to scale beyond a few servers. +Luckily, you are not locked to any specific cloud provider or technology. Since your entire application is already containerized, you can easily migrate to a Kubernetes based solution or a managed container service like AWS ECS, Google GKE or Azure AKS. diff --git a/docs/storage.md b/docs/storage.md new file mode 100644 index 0000000..383c56b --- /dev/null +++ b/docs/storage.md @@ -0,0 +1,21 @@ +# File Storage + +freePaaS doesn't offer built-in file storage solutions by default. + +Secure storage is a complex topic with many considerations. We recommend using specialized third-party services that are designed to handle file storage securely and efficiently. All major cloud providers offer object storage solutions (compatible with AWS S3) that can be easily integrated into your application. + +> [!IMPORTANT] +> Web containers in freePaaS are designed to be stateless, meaning that any files stored locally within the container will be lost if the container is restarted or redeployed. Therefore, it's crucial to use external storage solutions for any files that need to persist beyond the lifecycle of a single container instance. + +## Local storage (not recommended) + +If you still want to use local storage for development or testing purposes, you can mount a volume to your web container by modifying the `volumes` section in the [`containers/web/compose.yml`](../containers/web/compose.yml) file. + +```yaml +services: + web: + volumes: + - /path/on/host:/path/in/container:z +``` + +This will mount the specified host directory to the container, allowing files to persist across container restarts. However, be aware that this approach may lead to data loss and performance issues in a production environment.