Hosting multiple Rails 8 apps on a VPS using Kamal 2
I couldn’t find a tutorial that hosts multiple Rails 8 apps with Postgres databases on a single VPS(I use the cheapest Hetzner one). So here it is…
Hosting Multiple Rails 8 Apps with PostgreSQL on a Single VPS
This guide walks through deploying two (or more) independent Rails 8 applications — each with its own PostgreSQL database — onto one VPS using Kamal. Every app gets its own subdomain, SSL certificate, and isolated database, while sharing a single reverse proxy and one physical server.
What you’ll end up with:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────┐
app1.example.com ─┤ │
│ kamal-proxy (ports 80/443, SSL) │
app2.example.com ─┤ │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ VPS (e.g. 136.534.162.123) │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ app1 │ │ app2 │ │
│ │ (Rails) │ │ (Rails) │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ┌────▼────┐ ┌────▼────┐ │
│ │ app1-db │ │ app2-db │ │
│ │ Postgres│ │ Postgres│ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────────┘
Prerequisites
Before you start, make sure you have:
| Requirement | Notes |
|---|---|
| A VPS | Any provider (Hetzner, DigitalOcean, Linode, etc.). Ubuntu 22.04+ works well. |
| Root SSH access | ssh root@YOUR_SERVER_IP should work with your SSH key. |
| A domain | e.g. example.com with DNS you control. |
| Docker Hub account | Kamal builds images locally and pushes them to a registry the server can pull from. |
| Ruby & Docker locally | To build images and run Kamal commands. Just follow the instructions |
| Two Rails 8 apps | Created with --database=postgresql. |
Step 1: Create Your Rails Apps
Rails 8 ships with Kamal, Docker, and the Solid stack (cache, queue, cable) out of the box. Create each app with PostgreSQL:
1
2
rails new app1 --css tailwind --database=postgresql
rails new app2 --css tailwind --database=postgresql
Each app will already contain:
Dockerfile— production-ready, withpostgresql-clientinstalledconfig/deploy.yml— Kamal configuration (needs editing).kamal/secrets— secret management templatebin/kamal— Kamal CLI wrapper
Rails 8’s default database.yml production section uses four databases per app (primary, cache, queue, cable). We’ll configure all of them to talk to the same Postgres accessory.
Step 2: Configure DNS
Point each subdomain at your VPS before deploying. Let’s Encrypt needs the domains to resolve to issue SSL certificates.
| Type | Name | Value |
|---|---|---|
| A | app1.example.com | YOUR_SERVER_IP |
| A | app2.example.com | YOUR_SERVER_IP |
Step 3: Set Up Secrets
Kamal reads secrets from .kamal/secrets at deploy time. Never put raw passwords in this file, just pull them from environment variables instead.
Export these (add them to ~/.zshrc ~/.bashrc or a password manager):
1
2
3
4
5
6
7
8
# Docker Hub access token (not your account password)
# Create under Personal Access Tokens, with Read & Write permission
export KAMAL_REGISTRY_PASSWORD="dckr_pat_..."
# Strong random passwords. Save these! They're baked into the Postgres
# data volume on first boot and must stay the same for future deploys.
export APP1_DATABASE_PASSWORD="pass"
export APP2_DATABASE_PASSWORD="pass2"
Then configure .kamal/secrets in each app. The file is safe to commit to git because it only references env vars.
app1/.kamal/secrets:
1
2
3
4
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
RAILS_MASTER_KEY=$(cat config/master.key)
APP1_DATABASE_PASSWORD=$APP1_DATABASE_PASSWORD
POSTGRES_PASSWORD=$APP1_DATABASE_PASSWORD
app2/.kamal/secrets:
1
2
3
4
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
RAILS_MASTER_KEY=$(cat config/master.key)
APP2_DATABASE_PASSWORD=$APP2_DATABASE_PASSWORD
POSTGRES_PASSWORD=$APP2_DATABASE_PASSWORD
The POSTGRES_PASSWORD line is important: Kamal’s Postgres accessory reads POSTGRES_PASSWORD, while Rails reads APP1_DATABASE_PASSWORD / APP2_DATABASE_PASSWORD. Aliasing them to the same value keeps the app and database in sync.
Step 4: Configure Kamal (config/deploy.yml)
This is the main configuration file. Below is a complete example for app1. The app2 file is identical except for names (service, image, host, DB user, env vars).
app1/config/deploy.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
service: app1
image: YOUR_DOCKERHUB_USERNAME/app1
servers:
web:
- YOUR_SERVER_IP
# kamal-proxy handles SSL and routes traffic by hostname.
# Both apps share one proxy on ports 80/443.
proxy:
ssl: true
host: app1.example.com
registry:
username: YOUR_DOCKERHUB_USERNAME
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
- APP1_DATABASE_PASSWORD
clear:
SOLID_QUEUE_IN_PUMA: true
DB_HOST: app1-db
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password"
volumes:
- "app1_storage:/rails/storage"
asset_path: /rails/public/assets
builder:
arch: amd64
# Each app gets its own Postgres container (accessory).
# Kamal names it "app1-db" (service name + accessory name).
accessories:
db:
image: postgres:17
host: YOUR_SERVER_IP
env:
clear:
POSTGRES_USER: app1
POSTGRES_DB: app1_production
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
Key settings explained
| Setting | Purpose |
|---|---|
service | Unique name per app. Kamal prefixes containers, volumes, and networks with this. |
proxy.ssl + proxy.host | Enables Let’s Encrypt SSL and tells kamal-proxy which hostname routes to this app. |
DB_HOST: app1-db | The hostname of the Postgres accessory on Kamal’s private Docker network. |
accessories.db | Runs a dedicated Postgres 17 container. |
volumes | Persists Active Storage uploads across deploys. |
asset_path | Bridges fingerprinted assets between deploys to avoid 404s on in-flight requests. |
The app2 difference
For app2, change every app1 reference:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
service: app2
image: YOUR_DOCKERHUB_USERNAME/app2
proxy:
host: app2.example.com
env:
secret:
- APP2_DATABASE_PASSWORD
clear:
DB_HOST: app2-db
volumes:
- "app2_storage:/rails/storage"
accessories:
db:
env:
clear:
POSTGRES_USER: app2
POSTGRES_DB: app2_production
...
Both apps point accessories.db.host at the same YOUR_SERVER_IP. Kamal runs each accessory as a separate container on that host.
Step 5: Configure the Database (config/database.yml)
Rails 8’s generated database.yml already has the right structure for multi-database production. You only need to add two things to the production primary block:
- A
passwordfrom an environment variable - A
hostpointing at the Kamal accessory
app1/config/database.yml (production section):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
production:
primary: &primary_production
<<: *default
database: app1_production
username: app1
password: <%= ENV["APP1_DATABASE_PASSWORD"] %>
host: <%= ENV["DB_HOST"] %>
cache:
<<: *primary_production
database: app1_production_cache
migrations_paths: db/cache_migrate
queue:
<<: *primary_production
database: app1_production_queue
migrations_paths: db/queue_migrate
cable:
<<: *primary_production
database: app1_production_cable
migrations_paths: db/cable_migrate
The &primary_production anchor means cache, queue, and cable inherit host, username, and password from primary, so they all connect to the same Postgres server but use separate databases.
For app2, mirror the same pattern with app2 / APP2_DATABASE_PASSWORD.
Step 6: Enable SSL in Production
Because kamal-proxy terminates SSL, Rails needs to know all requests arrive over HTTPS. Uncomment (or add) these two lines in config/environments/production.rb:
1
2
config.assume_ssl = true
config.force_ssl = true
Without these, Rails may generate http:// URLs and redirect loops can occur behind the proxy.
Step 7: Validate Configuration
Before deploying, sanity-check each app’s config:
1
2
3
4
5
cd app1
bin/kamal config # should print the resolved YAML without errors
cd ../app2
bin/kamal config
Fix any errors before proceeding.
Step 8: Deploy
Commit your changes first — Kamal builds from your git checkout:
1
2
3
cd app1
git add -A && git commit -m "Configure Kamal deployment"
bin/kamal setup
kamal setup on the first app does everything:
- Installs Docker on the VPS (if not present)
- Boots the
app1-dbPostgres accessory - Builds the Docker image locally
- Pushes it to Docker Hub
- Starts kamal-proxy on ports 80 and 443
- Boots the Rails app container
- Requests a Let’s Encrypt certificate for
app1.example.com
Rails’ Docker entrypoint runs db:prepare on boot, which creates and migrates all four databases automatically.
Then deploy the second app:
1
2
3
cd ../app2
git add -A && git commit -m "Configure Kamal deployment"
bin/kamal setup
The second setup reuses the existing kamal-proxy and Docker installation. It boots app2-db, deploys the app, and registers app2.example.com as a second hostname on the proxy.
Visit https://app1.example.com, https://app2.example.com and both should be live with valid SSL.
Subsequent deploys
After the initial setup, updates are a single command:
1
bin/kamal deploy
How It Works
One proxy, many apps
Kamal 2 uses kamal-proxy instead of Traefik. A single proxy container listens on ports 80 and 443. When you deploy multiple apps to the same server, each app’s proxy.host registers as a routing rule. Incoming requests are matched by Host header and forwarded to the correct app container.
The first app to run kamal setup creates the proxy. Every subsequent app piggybacks on it.
Isolated databases via accessories
Each app’s Postgres runs as a Kamal accessory, which is a separate Docker container managed by Kamal but not part of the app’s deploy lifecycle. Accessories:
- Have their own persistent data directory on the server
- Are reachable only on Kamal’s private Docker network (no ports exposed to the internet)
- Are named
{service}-{accessory}— e.g.app1-db,app2-db
This gives you database isolation without running a full database server or managing users/databases manually.
Container naming
Kamal prefixes everything with the service name to avoid collisions:
| Resource | app1 | app2 |
|---|---|---|
| App container | app1-web-* | app2-web-* |
| DB accessory | app1-db | app2-db |
| Storage volume | app1_storage | app2_storage |
| Docker network | kamal (shared) | kamal (shared) |
Day-to-Day Operations
Useful aliases
Both apps ship with Kamal aliases in deploy.yml:
1
2
3
4
5
bin/kamal logs # Tail application logs
bin/kamal console # Rails console on the server
bin/kamal shell # Bash shell inside the container
bin/kamal dbc # Database console (psql)
bin/kamal app details # Container status and version
Running migrations
Migrations run automatically on deploy via db:prepare in the Docker entrypoint. To run them manually:
1
bin/kamal app exec "bin/rails db:migrate"
Checking server health
1
2
3
bin/kamal app details
bin/kamal proxy details
bin/kamal accessory details db
Or SSH in directly:
1
2
3
4
ssh root@YOUR_SERVER_IP
docker ps # All running containers
docker logs app1-web-<hash> # App logs
docker logs app1-db # Postgres logs
Adding a Third (or Nth) App
The pattern scales linearly. For app3:
rails new app3 --database=postgresql- Copy the Kamal config pattern from
app1, changing all names - Add a DNS A record:
app3.example.com → YOUR_SERVER_IP - Export
APP3_DATABASE_PASSWORD bin/kamal setup
No changes needed to existing apps. The proxy picks up the new hostname automatically.
Removing an App
To tear down a test app without affecting others:
1
2
3
4
5
6
7
cd app1
# Remove the Rails app containers
bin/kamal app remove
# Remove the Postgres accessory
bin/kamal accessory remove db
The kamal-proxy and other apps keep running. To also clean up persistent data on the server:
1
2
3
4
ssh root@YOUR_SERVER_IP
docker volume rm app1_storage
rm -rf ~/app1-db
Warning: This permanently deletes the database. Only do this for apps you no longer need.
To remove the shared proxy (only when no apps remain on the server):
1
bin/kamal proxy remove
Troubleshooting
SSL certificate fails on first deploy
Let’s Encrypt can’t issue a cert if DNS doesn’t resolve yet. Wait for propagation, then run bin/kamal deploy again.
Database connection refused
Check that the accessory is running:
1
bin/kamal accessory details db
Common causes:
DB_HOSTindeploy.ymldoesn’t match the accessory name (app1-db, notdborlocalhost)host:is missing fromdatabase.ymlproduction configAPP1_DATABASE_PASSWORDandPOSTGRES_PASSWORDdon’t match
502 Bad Gateway
The app container may still be booting or crashed. Check logs:
1
bin/kamal logs
kamal-proxy uses Rails 8’s health check endpoint /up to know when the app is ready.
“Container not found” on console/shell
The container may have stopped. Check status and redeploy:
1
2
bin/kamal app details
bin/kamal deploy
Uncommitted changes warning
Kamal builds from git. Commit or stash changes before deploying:
1
2
git add -A && git commit -m "Your changes"
bin/kamal deploy
Complete Checklist
Use this as a quick reference when setting up a new app:
- Rails app created with
--database=postgresql - DNS A record pointing subdomain to VPS IP
KAMAL_REGISTRY_PASSWORDexported (Docker Hub access token)APP_DATABASE_PASSWORDexported (strong random value, saved securely)config/deploy.yml— server IP, proxy host, registry, DB accessoryconfig/database.yml—hostandpasswordin production primaryconfig/environments/production.rb—assume_sslandforce_sslenabled.kamal/secrets— registry password, master key, DB passwordbin/kamal configpasses without errors- Changes committed to git
bin/kamal setup(first deploy) orbin/kamal deploy(updates)
Summary
Hosting multiple Rails 8 apps on one VPS with Kamal boils down to:
- One kamal-proxy routes traffic by subdomain with automatic SSL
- One Postgres accessory per app gives isolated databases without extra infrastructure
- Unique
servicenames prevent container and volume collisions - Environment variables connect each app to its own database over the private Docker network
Each app deploys independently. The whole setup costs a single VPS(6.59EUR per month for the smallest Hetzner machine cx23) and scales to as many apps as the server can handle.
Bonus
DHH uses 1password for Omarchy, so I am giving it a shot. Here is how to setup a password for the databases:
1
2
op vault create Kamal
op item create --vault Kamal --category login --title "app1" --generate-password
So here is how the new file looks
1
2
3
4
5
6
7
8
SECRETS=$(kamal secrets fetch --adapter 1password --account my.1password.com --from Kamal docker-hub/password app1-db/password)
KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract Kamal/docker-hub ${SECRETS})
APP1_DATABASE_PASSWORD=$(kamal secrets extract Kamal/app1-db ${SECRETS})
POSTGRES_PASSWORD=$APP1_DATABASE_PASSWORD
RAILS_MASTER_KEY=$(cat config/master.key)
Pitfalss
Not commiting stuff
In order for your changes to be “seen” by Kamal, you need to commit them… No need to push, but commiting them is neccessery
Failed to connect to the docker API at unix:///home/$USER/
On kamal setup:
1
2
3
4
5
6
DEBUG [a9f5e9f0] Command: docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t yoiv/supporter:6af216b1008a5c90ef34fa2fffac600e5b96f839 -t yoiv/supporter:latest --label service="supporter" --file Dockerfile . 2>&1
DEBUG [a9f5e9f0] ERROR: failed to build: failed to connect to the docker API at unix:///home/mint/.docker/desktop/docker.sock; check if the path is correct and if the daemon is running: dial unix /home/mint/.docker/desktop/docker.sock: connect: no such file or directory
Finished all in 2.1 seconds
ERROR (SSHKit::Command::Failed): docker exit status: 256
docker stdout: ERROR: failed to build: failed to connect to the docker API at unix:///home/mint/.docker/desktop/docker.sock; check if the path is correct and if the daemon is running: dial unix /home/mint/.docker/desktop/docker.sock: connect: no such file or directory
docker stderr: Nothing written
Fix: official docs
To create the docker group and add your user:
- Create the docker group.
1
sudo groupadd docker - Add your user to the docker group.
1
sudo usermod -aG docker $USER
Log out and log back in so that your group membership is re-evaluated.
If you’re running Linux in a virtual machine, it may be necessary to restart the virtual machine for changes to take effect.
You can also run the following command to activate the changes to groups:
1
newgrp docker
Missing secret_key_base for ‘production’ environment, set this string with bin/rails credentials:edit
Error:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
INFO [4b10a14d] Running docker container ls --all --filter 'name=^supporter-web-7320b5702cc96aaa02a4de3029c82c9d7ef1d632$' --quiet | xargs docker logs --timestamps 2>&1 on 167.233.112.40
INFO [4b10a14d] Finished in 0.338 seconds with exit status 0 (successful).
ERROR 2026-07-21T11:24:35.998574044Z bin/rails aborted!
2026-07-21T11:24:35.999806438Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:35.999814678Z
2026-07-21T11:24:35.999817098Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:35.999819268Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:35.999895378Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:35.999952259Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:35.999975629Z (See full trace by running task with --trace)
2026-07-21T11:24:37.364258126Z bin/rails aborted!
2026-07-21T11:24:37.365486480Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:37.365495920Z
2026-07-21T11:24:37.365499290Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:37.365502460Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:37.365587760Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:37.365641300Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:37.365678260Z (See full trace by running task with --trace)
2026-07-21T11:24:38.844590032Z bin/rails aborted!
2026-07-21T11:24:38.846401048Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:38.846412798Z
2026-07-21T11:24:38.846416948Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:38.846421428Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:38.846558068Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:38.846648628Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:38.846699589Z (See full trace by running task with --trace)
2026-07-21T11:24:40.483993732Z bin/rails aborted!
2026-07-21T11:24:40.485458137Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:40.485467527Z
2026-07-21T11:24:40.485470847Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:40.485474067Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:40.485586627Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:40.485638968Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:40.485662468Z (See full trace by running task with --trace)
2026-07-21T11:24:42.596931672Z bin/rails aborted!
2026-07-21T11:24:42.598150136Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:42.598159436Z
2026-07-21T11:24:42.598162746Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:42.598165966Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:42.598243287Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:42.598291737Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:42.598316017Z (See full trace by running task with --trace)
2026-07-21T11:24:45.446263631Z bin/rails aborted!
2026-07-21T11:24:45.447465006Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:45.447474336Z
2026-07-21T11:24:45.447477766Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:45.447480996Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:45.447574226Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:45.447614166Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:45.447657626Z (See full trace by running task with --trace)
2026-07-21T11:24:49.899540493Z bin/rails aborted!
2026-07-21T11:24:49.900780087Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:49.900790237Z
2026-07-21T11:24:49.900793567Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:49.900796787Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:49.900885488Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:49.900935008Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:49.900981878Z (See full trace by running task with --trace)
2026-07-21T11:24:57.560624950Z bin/rails aborted!
2026-07-21T11:24:57.561809375Z ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
2026-07-21T11:24:57.561818395Z
2026-07-21T11:24:57.561821715Z raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`"
2026-07-21T11:24:57.561824855Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21T11:24:57.561942345Z /rails/config/environment.rb:5:in `<main>'
2026-07-21T11:24:57.561947665Z Tasks: TOP => db:prepare => db:load_config => environment
2026-07-21T11:24:57.561950795Z (See full trace by running task with --trace)
INFO [ecb1fcd7] Running docker container ls --all --filter 'name=^supporter-web-7320b5702cc96aaa02a4de3029c82c9d7ef1d632$' --quiet | xargs docker inspect --format '' on 167.233.112.40
INFO [ecb1fcd7] Finished in 0.276 seconds with exit status 0 (successful).
ERROR null
INFO [46fbab4d] Running docker container ls --all --filter 'name=^supporter-web-7320b5702cc96aaa02a4de3029c82c9d7ef1d632$' --quiet | xargs docker stop on 167.233.112.40
INFO [46fbab4d] Finished in 0.311 seconds with exit status 0 (successful).
Finished all in 53.6 seconds
Releasing the deploy lock...
Finished all in 55.7 seconds
ERROR (SSHKit::Command::Failed): Exception while executing on host 167.233.112.40: docker exit status: 1
docker stdout: Nothing written
docker stderr: Error: target failed to become healthy within configured timeout (30s)
It says it in the logs, but just set secret_key_base for ‘production’ environment, set this string with EDITOR="code --wait" rails credentials:edit -e production
1
secret_key_base: "some random secure symbols"
Using pgvector
In order to use a vector db we need a different Postgress image.
1
2
3
accessories:
db:
image: pgvector/pgvector:pg17 # instead of postgres:17
1
bin/kamal accessory reboot db
this gave me this:
1
2
ERROR 2026-07-21T13:32:29.959762691Z WARNING: database "supporter_production" has a collation version mismatch
2026-07-21T13:32:29.959781511Z DETAIL: The database was created using collation version 2.41, but the operating system provides version 2.36.
PostgreSQL will still start and Rails will still connect. It usually happens when: the database was created using one Linux image/libc version, then you switched to another image (for example, from postgres:17 to pgvector/pgvector:pg17).
Real fix (when we don’t have production data). We do this only if it is our initial deploy.
1
2
kamal accessory remove db
kamal accessory boot db