# Kamal (Deploy to any VPS)

> Deploy Pegasus to any Linux VPS using Kamal with automated SSL certificates, load balancing, and PostgreSQL database management.

Pegasus supports container-based deployment to any Linux server using [Kamal](https://kamal-deploy.org/).

Kamal is a deployment tool that uses Docker to deploy applications to servers. It is designed to be simple to use and to work with a single server or a cluster of servers. It can also be used to deploy multiple apps to the same server.

Kamal will deploy the app as Docker containers, and will also deploy the database and any other services that are required. It will also configure a load balancer ([kamal-proxy](https://github.com/basecamp/kamal-proxy)) to route traffic to the app and configure SSL certificates using LetsEncrypt.

By default, Pegasus will run all the services on a single server, but Kamal is designed to work with multiple servers, so you can easily move services to separate servers and update the Kamal configuration in `config/deploy.yml`.

### Screencast

[Section titled “Screencast”](#screencast)

You can watch a screencast showing how to deploy to a Hetzner server with Kamal here:

Or follow along with the documentation below.

### Overview

[Section titled “Overview”](#overview)

Deploying on Kamal will require a few pieces:

1. A server running Linux (the latest Ubuntu LTS is recommended---version 24.04 as of this writing) and accessible via SSH.
2. A domain name for your app. You will need to create a DNS record pointing to your server’s IP address.
3. A Docker registry to store your images. You can use [Docker Hub](https://hub.docker.com) or any other registry.
4. A development environment where you install and configure Kamal.

We’ll walk through these in more detail in order below.

### Provision and prepare your server

[Section titled “Provision and prepare your server”](#provision-and-prepare-your-server)

The first step is to provision a server where you will host your application. Some popular choices include:

* Hetzner (get €20 credit and support Pegasus with [this link](https://hetzner.cloud/?ref=49vhF1w3TIyB)).
* Digital Ocean Droplets (get $100 credit and support Pegasus with [this link](https://m.do.co/c/432e3abb37f3)).
* [Linode](https://www.linode.com/).
* [AWS](https://aws.amazon.com/) (Lightsail or EC2).
* [Google Cloud](https://cloud.google.com/).
* [Microsoft Azure](https://azure.microsoft.com/en-us).

It is recommended to choose the latest Ubuntu LTS---version 24.04 as of this writing---for your operating system. Other operating systems might work, but are not tested or officially supported.

We also recommend at least 2GB of RAM.

Once you’ve chosen a hosting company and provisioned a server, follow the instructions provided to login (SSH) to the server. You will need to be able to log in remotely to complete the rest of the setup.

The rest of these instructions will run kamal as the root user. If you prefer to run kamal as a different user---which can prevent certain kinds of attacks---see the note below.

### Set up DNS

[Section titled “Set up DNS”](#set-up-dns)

To set up SSL you will need a DNS record pointing at your server. Create a new “A” record using whatever tool you use to manage your DNS, and point it at the IP address of the server you created above.

The most common domain to use is `www.<yourdomain>.com`.

### Create the image repository

[Section titled “Create the image repository”](#create-the-image-repository)

Before doing deployment, you need a place to store your Docker images, also known as a *Docker registry*. The two recommended options are [Docker Hub](https://hub.docker.com/) and [Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/).

Docker Hub is easier to use and good if you only have a single project (covered under their free tier). Amazon ECR is much more cost-effective if you have multiple projects---typically costing only pennies per project, per month.

You can choose another registry if you want, as described in the [Kamal docs](https://kamal-deploy.org/docs/configuration).

#### Using Docker Hub

[Section titled “Using Docker Hub”](#using-docker-hub)

First create an account on [Docker Hub](https://hub.docker.com/) and note your username.

Then create a new repository, choosing a unique name for your app, and **marking it “private”**.

Finally you will need to create an access token. Go to “Account Settings” —> “Security” and make a new access token, giving it the default permissions of Read, Write, Delete. **Save this token somewhere as you will need it in the next step and will only see it once.**

#### Using Amazon ECR

[Section titled “Using Amazon ECR”](#using-amazon-ecr)

You will need an [AWS account](https://aws.amazon.com/) to use Amazon ECR.

1. Login to the AWS console and navigate to the ECR service.
2. Under “Private Registry” —> “Repositories” click “Create repository”.
3. Choose a repository name, e.g. “myaccount/myapp” and keep the remaining defaults. Click “Create”.
4. Next, in the IAM service, create an IAM user / group, giving it the `AmazonEC2ContainerRegistryFullAccess` permissions policy.
5. Create an access key for the user / group and save the credentials.
6. [Install and configure the AWS CLI](https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-configure.html) on your local machine, using those credentials.

You can test that everything is working by running:

```plaintext
aws ecr get-login-password
```

And confirming it returns a (very long!) password.

For a more detailed guide on setting up Amazon ECR with Kamal, see [this detailed writeup](https://dylancastillo.co/posts/deploy-a-django-app-with-kamal-aws-ecr-and-github-actions.html).

### Install and configure Kamal

[Section titled “Install and configure Kamal”](#install-and-configure-kamal)

Finally, we can set everything up to deploy our production application with Kamal. If you have a Ruby environment available, you can install Kamal globally with:

```bash
gem install kamal
```

*Note: you may want to use [`rbenv`](https://github.com/rbenv/rbenv) to manage your environment.*

If you don’t have Ruby running you can also use Docker to install Kamal, by creating an alias command as described [in the Kamal docs here](https://kamal-deploy.org/docs/installation/).

#### Create `secrets` file in the `.kamal` directory

[Section titled “Create secrets file in the .kamal directory”](#create-secrets-file-in-the-kamal-directory)

Kamal expects a `.kamal/secrets` file in this folder which will contain all the environment variables needed for deployment. The `secrets` file should not be checked into source control. See `.kamal/secrets.example` for the required variables.

```bash
cp .kamal/secrets.example .kamal/secrets
```

#### Update the Kamal deploy.yml file

[Section titled “Update the Kamal deploy.yml file”](#update-the-kamal-deployyml-file)

The Kamal configuration is in `config/deploy.yml`. You will need to update the following values:

* Docker image repo: `image: <namespace>/<repository-name>` - this is the repository you created above. If you’re using Docker Hub, the `namespace` will typically be your username, and if you’re using ECR you can use the exact repository name you chose above.
* Docker registry settings (see below)
* Your server IP address (or hostname) `<IP-ADDRESS>` (this value is listed once per service).
* Your app domain: `host: <your site url>` in the `proxy` section at the end, if this is not already set via your project configuration.

For the registry section, if you’re using **Docker Hub**, you will need to add the username you chose above:

```yaml
registry:
  username: <DOCKER REGISTRY USERNAME>  # set this
  password:
    - KAMAL_REGISTRY_PASSWORD
```

If you’re using **Amazon ECR**, you should update it as follows:

```yaml
registry:
  server: <your aws account id>.dkr.ecr.<your aws region id>.amazonaws.com
  username: AWS
  password: <%= %x(aws ecr get-login-password) %>
```

For other registries, see the [Kamal docs](https://kamal-deploy.org/docs/configuration/docker-registry/).

#### Update the .kamal/secrets file

[Section titled “Update the .kamal/secrets file”](#update-the-kamalsecrets-file)

Next, in your `.kamal/secrets` file you should add the following variables:

* If you’re using Docker Hub, set `KAMAL_REGISTRY_PASSWORD` to the access token value you created above. If you’re using Amazon ECR, you can remove this variable.
* Choose secure, unique, and ideally random values for `POSTGRES_PASSWORD` and `SECRET_KEY`.
* Update the `DATABASE_URL` value (use the same password as `POSTGRES_PASSWORD`).

You can review other settings in `deploy.yml`, but those should be all that you need to set yourself for your first deployment.

### Deploy

[Section titled “Deploy”](#deploy)

Finally, we can use Kamal to do the rest of the setup. Run the following on your *local* machine, from the project root directory.

```bash
kamal setup
```

This will perform all the tasks necessary to deploy your application (duplicated below from the [Kamal docs](https://kamal-deploy.org/docs/installation)):

* Connect to the servers over SSH (using root by default, authenticated by your SSH key).
* Install Docker on any server that might be missing it (using get.docker.com): root access is needed via SSH for this.
* Log into the registry both locally and remotely.
* Build the image using the standard Dockerfile in the root of the application.
* Push the image to the registry.
* Pull the image from the registry onto the servers.
* Ensure kamal-proxy is running and accepting traffic on ports 80 and 443.
* Start a new container with the version of the app that matches the current Git version hash.
* Tell kamal-proxy to route traffic to the new container once it is responding with 200 OK to GET /up.
* Stop the old container running the previous version of the app.
* Prune unused images and stopped containers to ensure servers don’t fill up.

If everything is set up properly then in five or so minutes you should be able to visit your new application at the configured domain. You’re done!

### Post-deployment steps

[Section titled “Post-deployment steps”](#post-deployment-steps)

Once you’ve gotten everything set up, head on over to the [production checklist](/deployment/production-checklist) and run through everything there. In particular, you will have to set up media files using an external service like S3.

#### Manage changes after initial deployment

[Section titled “Manage changes after initial deployment”](#manage-changes-after-initial-deployment)

See the `config/README.md` file in your project repo for pointers on managing the production environment after the initial deployment.

The main command you will regularly run is `kamal deploy`, which will push new releases and configurations of your application.

### Settings and Secrets

[Section titled “Settings and Secrets”](#settings-and-secrets)

Kamal builds use the `settings_production.py` file. You can add settings here, and use environment variables to manage any secrets, following the pattern used throughout the file. If you modify `settings_production.py` (or any other code) you will need to run:

```bash
kamal deploy
```

To push the changes to your servers.

Secrets should be managed in environment variables. To add new environment variables you will need to update them in two places:

1. The variable *name* needs to be added to the `env` section at the top of `config/deploy.yml`.
2. The variable name *and value* needs to be added to `.kamal/secrets` (the same `secrets` file we’ve been using above).

You can see examples of this for variables like `DATABASE_URL` in those two files.

Once you modify your environment variable files you will need to run:

```bash
kamal deploy
```

To update the variables on the server and redeploy the app.

### Running one-off commands

[Section titled “Running one-off commands”](#running-one-off-commands)

The easiest way to run one-off commands on your server is to use the `kamal app exec` command. For example:

```bash
kamal app exec -r web 'python manage.py bootstrap_subscriptions'
```

If you want an interactive SSH-style shell you can run:

```bash
kamal app exec -i bash
```

You should now have a shell where you can run any Python/`manage.py` command.

You can also get a database shell by running:

```bash
kamal accessory exec postgres -i 'psql -h localhost -p 5432 -U <youruser>' --reuse
```

For more information see [Kamal commands](https://kamal-deploy.org/docs/commands).

### Troubleshooting

[Section titled “Troubleshooting”](#troubleshooting)

#### Something went wrong during setup

[Section titled “Something went wrong during setup”](#something-went-wrong-during-setup)

If the `kamal setup` command fails it should print out the error it got. Once you’ve resolved it, you may need to set up the services individually instead of re-running it. You can do that with the commands below:

```bash