A curated list of Ruby on Rails SaaS boilerplates.

Articles Suggest a boilerplate

How to configure Postgres as an Accessory with Kamal 2 and Rails 8 on a single server

Read as TXT for LLMs

What you'll learn

In this guide I will explain how I configured and deployed a Rails 8.0.2 application using Kamal 2 with a Postgres database running as a kamal accessory on a single Virtual Private Server (VPS).

Prerequisites

  • A rails 8 application (I was using 8.0.2)
  • A Postgres database (I was using 15)
  • A VPS running Ubuntu (I was using 24.04)

Assumptions

You have a locally working Rails 8 application that is using postgresql for its database.

Errors you might be seeing

ActiveRecord::ConnectionNotEstablished: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory (ActiveRecord::ConnectionNotEstablished)
        Is the server running locally and accepting connections on that socket?

How to get it working

Step 1: Configuring the Kamal accessory

In the kamal deploy.yml file and in the docs, the only reference is mysql, and it's not clear which pieces of the configuration are specific to mysql and which you might have to change for postgres.

🚨 The name of the accessory is important. It will be used in the DATABASE_URL and in the specific container name. In this example we are using postgres.

accessories:
  postgres:
    image: postgres:15
    host: <YOUR VPS IP ADDRESS>
    port: "127.0.0.1:5432:5432"
    env:
      clear:
        POSTGRES_USER: <DB USERNAME OF YOUR CHOICE>
        POSTGRES_DB: <YOUR RAILS APP NAME>_production
        DB_HOST: 127.0.0.1
        DB_PORT: 5432
      secret:
        - POSTGRES_PASSWORD
    directories:
      - data:/var/lib/postgresql/data
    files:
      - db/production_setup.sql:/docker-entrypoint-initdb.d/setup.sql
  1. Replace <YOUR VPS IP ADDRESS> with your VPS IP address. This tells kamal on which host it should deploy the accessory.
  2. Replace <YOUR RAILS APP NAME> with the name of your Rails application.
  3. Replace <DB USERNAME OF YOUR CHOICE> with the username of your choice. This can be anything you want but something like rails_user is common.
  4. Replace - db/production_setup.sql:/docker-entrypoint-initdb.d/setup.sql with the path to your production_setup.sql file. See the example below.

⚠️ The use of 127.0.0.1 is important. Kamal will use the 127.0.0.1 address to connect to the postgres accessory which means it will only connect to the postgres accessory running on the same server and not expose the port to the outside public internet. In a scenario where you had a private IP on a private subnet, you could tell rails to use that address which would mean "connect to the DB accessory at this address, not locally on the same host."

✅ After this step you should have your accessory correctly defined in your deploy.yml file.

Step 2: Configure your kamal secrets

This is an important step that's easy to mess up because there are a lot of moving parts.

You have to:

  1. Configure the kamal secrets in .kamal/secrets
  2. Configure the environment variables in deploy.yml so they are passed to the container running your rails app and to the container running the postgres accessory.
  3. Configure your .env (or 1password) with the correct secrets so they can be picked up and used by kamal.

Here is what the secrets file for this example looks like:

# Grab the registry password from ENV
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD

### We added these
DATABASE_URL=$DATABASE_URL
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
POSTGRES_USER=$POSTGRES_USER
POSTGRES_DB=$POSTGRES_DB
DB_HOST=$DB_HOST
DB_PORT=$DB_PORT
DATABASE_URL=$DATABASE_URL
### end of the things we added

# Improve security by using a password manager. Never check config/master.key into git!
RAILS_MASTER_KEY=$(cat config/master.key)

Step 3: Add the postgres setup script

This is the script that will be run when the postgres container is created. It will create the databases and the user with the correct permissions.

It goes in the db directory of your Rails application. And should match what we defined in the deploy.yml file in Step 1.

-- db/production_setup.sql

CREATE DATABASE <YOUR RAILS APP NAME>_production;
CREATE DATABASE <YOUR RAILS APP NAME>_production_cache;
CREATE DATABASE <YOUR RAILS APP NAME>_production_queue;
CREATE DATABASE <YOUR RAILS APP NAME>_production_cable;
  1. Replace <YOUR RAILS APP NAME> with the name of your Rails application.

✅ After this step you should have your postgres accessory correctly configured and ready to deploy with kamal. But our rails application is not yet configured to use it.

Step 4: Configuring database.yml

The simplest approach for configuring our rails application is to use a DATABASE_URL environment variable which we will use in our database.yml file.

You will notice in this example I am using solid queue, cache, and cable. You can remove these if you don't use them. But they are provided in the example for clarity and based on a newly created Rails 8 application.

production:
  primary: &primary_production
    <<: *default
    url: <%= ENV['DATABASE_URL'] %>
    database: widgetfactory_production
  cache:
    <<: *primary_production
    database: widgetfactory_production_cache
    migrations_paths: db/cache_migrate
  queue:
    <<: *primary_production
    database: widgetfactory_production_queue
    migrations_paths: db/queue_migrate
  cable:
    <<: *primary_production
    database: widgetfactory_production_cable
    migrations_paths: db/cable_migrate
  1. Replace <YOUR RAILS APP NAME> with the name of your Rails application.

✅ After this step your database.yml will be configured correctly to use DATABASE_URL.

Step 5: Construct the DATABASE_URL and add it to your .env (or 1Password) file

The DATABASE_URL is a connection string that is used to connect to the postgres database. It is a combination of the username, password, host, port, and database name.

🚨 Pay attention to SPECIFIC CONTAINER NAME in the example below. This is a bit of an undocumented gotcha that is easy to mess up because it seems insignificant but it's not.

SPECIFIC CONTAINER NAME = <YOUR RAILS APP NAME>-<WHAT YOU NAMED THE ACCESSORY IN THE DEPLOY.YML FILE>

For example if you had an app named widgetfactory and you named the accessory postgres in the deploy.yml file, then the specific container name would be widgetfactory-postgres.

DATABASE_URL=postgresql://<DB USERNAME OF YOUR CHOICE>:<DB PASSWORD OF YOUR CHOICE>@<SPECIFIC CONTAINER NAME>:5432/<YOUR RAILS APP NAME>_production
  1. Replace <DB USERNAME OF YOUR CHOICE> with the username of your choice.
  2. Replace <DB PASSWORD OF YOUR CHOICE> with the password of your choice.
  3. Replace <SPECIFIC CONTAINER NAME> with the specific container name of your accessory. Using the formula for the specific container name from the example above.
  4. Replace <YOUR RAILS APP NAME> with the name of your Rails application.

Now you need to add this string to your .env (or 1Password) file. I will not explain how to use 1Password here because it's documented elsewhere.

Your .env should look something like this, and should match the values you specified in the kamal secrets file. We'll use the application name widgetfactory for this example.

KAMAL_REGISTRY_PASSWORD=password
POSTGRES_USER=rails_user
POSTGRES_DB=widgetfactory_production
POSTGRES_PASSWORD=password123
DB_HOST=127.0.0.1
DB_PORT=5432
DATABASE_URL=postgresql://rails_user:password123@widgetfactory-postgres:5432/widgetfactory_production

✅ After this step you should have a DATABASE_URL in your .env (or 1Password) file that is correctly configured to connect to your postgres accessory.

Step 6: Deploy your application

If you haven't already deployed this application you can run kamal deploy now to deploy it and kamal will setup the postgres accessory and the rails application.

If you have already deployed this application you can run kamal accessory postgres boot postgres to just boot up the postgres accessory and make sure it's working.

Then you can run kamal deploy again to deploy the rails application.

Final note about DATABASE_URL in development

If you have done all of this using .env and not .env.production and your local environment is not setup exactly the same you will probably run into errors with the project locally.

That's because the presence of DATABASE_URL in your .env supercedes the configuration you have in database.yml.

To fix this you can either:

  1. use .env.production for your secrets that are relevant to the production environment.
  2. Configure your secrets in another password manager like 1Password and not use the .env file at all.

Other Resources