How to configure Postgres as an Accessory with Kamal 2 and Rails 8 on a single server
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
- Replace
<YOUR VPS IP ADDRESS>
with your VPS IP address. This tells kamal on which host it should deploy the accessory. - Replace
<YOUR RAILS APP NAME>
with the name of your Rails application. - Replace
<DB USERNAME OF YOUR CHOICE>
with the username of your choice. This can be anything you want but something likerails_user
is common. - 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:
- Configure the kamal secrets in
.kamal/secrets
- 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. - 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;
- 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
- 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
- Replace
<DB USERNAME OF YOUR CHOICE>
with the username of your choice. - Replace
<DB PASSWORD OF YOUR CHOICE>
with the password of your choice. - Replace
<SPECIFIC CONTAINER NAME>
with the specific container name of your accessory. Using the formula for the specific container name from the example above. - 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:
- use .env.production for your secrets that are relevant to the production environment.
- Configure your secrets in another password manager like 1Password and not use the .env file at all.