





















I'm , maker of PromptHero, Hustl, Jobician, and other products that are used by millions of people across the globe. My work has been featured in The Washington Post, The Wall Street Journal, Vox, Fast Company and others.
Last updated: Jan 2026. Now fully updated for Rails 8.1+ and Kamal 2.8+!
Rails 8 is out. And with it Kamal 2, the new default way of deploying Rails apps.
For those unfamiliar, Kamal is a tool that puts your Rails app in a Docker container, which makes it easier to deploy it anywhere. But it comes with its own learning curve.
I personally found Kamal hard for the uninitiated. Many things were missing in the official docs, and I didn’t find any good tutorial on how to get a full whole Rails app in production using Kamal 2 – from scratch to fully in production, accounting for all the moving pieces a Rails app usually needs.
I’ve made RailsFast, a production-ready Rails 8 boilerplate that comes with everything wired and working out of the box (including Kamal with PostgreSQL databases, backups, and all the config described in this blogpost!) Stop rebuilding auth, payments, and admin panels, and ship your next Rails app in a couple days, not weeks.
For example – there’s no good default example or guide on how to set up a Postgres database container for your Rails app to use. You need to dig deep into the Kamal docs and know very well your way around Docker to figure things out. There’s a decent amount of undocumented features too, and even unimplemented behavior that’s present in the docs but doesn’t acutally work in the real world (like the SSH keys array configuration option, which is documented but not implemented – just try it out)
It turns out configuring everything to run even a simple Rails app in production is non-trivial. In the past few weeks I managed to migrate a few of my production apps to Rails 8 and Kamal 2, and I spent a bunch of hours figuring all this out. I’m writing everything down in this blogpost so you can learn from my mistakes and avoid spending all that time figuring things out on your own.
Kamal claims that by running kamal setup it “runs everything required to deploy an application to a fresh host”, but it really doesn’t.
Kamal just installs Docker.
It installs Docker only if it was missing in the host machine, when running kamal setup. That’s it.
I managed to shoot myself in the foot and get my production Docker server hacked – mainly because I was unfamiliar with Docker and I changed the wrong config, but also because Kamal doesn’t really harden your instance and fails to make this clear in their messaging.
I think hardening is a crucial step. You can’t really have a production server running and sleep well at night if things aren’t properly hardened. Real production traffic from the internet is full of automated attacks and probing / testing malicious requests, and you better protect yourself against it. At the very least, you need to set up a firewall to block all ports except the absolutely necessary for your app to work; and fail2ban so people can’t just brute force their way into the machine.
I made a comprehensive script to thoroughly harden a fresh Ubuntu Server instance and make it fully ready to run production Docker containers.
You can ⬇️ download it from my GitHub Gist. Just ssh into your new Ubuntu server wget it, chmod +x it and run it.
Note: it’s good practice to always check scripts you find on the internet before you run them. Make sure they don’t do things you don’t want them to do. For example, ask Claude / ChatGPT to review and explain my script for you.
With Kamal, you now need a Docker registry to upload and pull your images from. You can’t just deploy your code straight to the server: it now has to be built into a Docker image, that Docker image needs to be uploaded to a remote container registry, then that same image needs to be downloaded from the container registry into the target production server. There’s rumors that Kamal will allow you to easily make your deployment server also work as a Docker registry in the future to avoid this external dependency, but for the time being, using a separate registry is the easiest – and the default.
I shopped around and considered a bunch of options, like GitHub (ghcr.io), Docker’s own container registry (Docker Hub), the DigitalOcean Container Registry, and even setting up your custom container registry using Cloudflare R2. The problem is if you need to keep your Docker images private, and deploy often to production (as you should), each service varies wildly in thresholds for the free tier / pricing for the private tier.
I found AWS Elastic Container Registry (ECR) was easiest and cheapest option (ironically, because I left the cloud last month)
The first thing you want to do is create an AWS account if you hadn’t already, configure billing and all that, and configure the aws CLI.
Then, on ECR, create an actual registry for each project you want to deploy with Kamal. You can name your ECR repositories anything you like, I chose to name mines following the typical Docker Hub naming convention username/projectname
While you’re still on your AWS ECR repo, set up a lifecycle policy rule under Repositories > Lifecycle Policy so only the most ~10-20 recent images are kept (~5GiB) and the old ones get auto deleted, so you don’t incur in big storage costs by keeping every single image you build.
ECR costs $1 per 10GiB; each Kamal image is roughly ~250Mb, so if you’re pushing to production twice a day, you’re increasing your total ECR storage by 1GiB every 4 days, so you’re roughly increasing your ECR costs by $1/mo every 40 days. It’s not much but it’s a waste to have stale images taking up space in your registry.
Now, there are also charges for data transfer.
In particular, AWS ECR charges $0.09 per GiB transferred OUT of the registry, if your repository is private and you’re deploying the images to servers outside of the AWS network (like Hetzner, for example). In the case you’re deploying twice a day, you’re transferring out 250Mb * 2 times/day * 30 days/month * $0.09 / GiB = ~15Gib / month * $0.09 / GiB = $1.35/mo
So you’d expect to pay around $2.35/mo for the registry and associated data transfers, PER repository, if you’re deploying to production twice a day every day and you have a private Docker registry.
To allow Kamal to access the Docker registry, you’ll need to configure the right access secrets on the .kamal/secrets file.
This is also partially why I found AWS ECR the easiest to set up. I can just write this on my secrets file:
# .kamal/secrets AWS_ECR_PASSWORD=$(aws ecr get-login-password --region us-east-1 --profile default)
and that’s enough for Kamal to access my ECR registry on production, provided I also set it right in deploy.yml, like this:
# config/deploy.yml password: - AWS_ECR_PASSWORD
If I were using something else, like Docker Hub or the GitHub Container Registry, I’d have to provide Kamal with access to my whole password manager, in my case using Kamal’s Bitwarden adapter, which would require me to:
# You'd need to commit this line when using a password manager like Bitwarden to store Kamal secrets: kamal secrets fetch --adapter bitwarden --account [email protected] REGISTRY_PASSWORD DB_PASSWORD
I found AWS not only cheaper, but also nicer in the way it shares secrets with Kamal because I can just use the aws CLI and avoid leaking my Bitwarden master admin email.
To run PostgreSQL in the same server as your Rails app, you’ll need to configure a postgres accesory and set up the right Postgres config across your Rails app.
This is not trivial.
To begin with, define your production postgres accessory in Kamal’s deploy.yml file:
# config/deploy.yml
# First, define the right database ENV variables in the env / clear section
# These are the ENV variables your RAILS app uses. They'll need to match your Postgres variables ofc
env:
clear:
DB_HOST: servicename-postgres
POSTGRES_USER: appname
POSTGRES_DB: appname_production
secret:
- POSTGRES_PASSWORD # Comes from the .kamal/secrets file
# you'll have here other keys like RAILS_MASTER_KEY etc.
# Then, configure the `postgres` accessory
accessories:
postgres:
image: postgres:15
host: x.x.x.x # The IP you want the database deployed to
env:
clear:
POSTGRES_USER: appname # Same as above
POSTGRES_DB: appname_production # Same as above
secret:
- POSTGRES_PASSWORD # Comes from the .kamal/secrets file
directories:
- data:/var/lib/postgresql/data
Few things going on here:
postgres (this name is important, we’ll use it later)postgres:15 Docker image for the database, which will get pulled from the Docker registry. The reason why we use the 15 tag and not the latest version (17 at the time of writing this blogpost) or the alpine version is deliberate. PostgreSQL 15 is the most mature, stable and long-term supported PostgreSQL version as of today, with extensions and tools thoroughly tested against 15, and the alpine versions, despite having a smaller footprint, are not as well suited for production use because they may not fully support some extensions, may lack security updates, debugging tools, etc.kamal Docker network, which has its own name resolution, so there’s no need to even expose a port or an IP to the host machine. The Rails app just connects to the appname-postgres container via Docker (appname = your app name; postgres = the name of the accessory, defined as the key of the accesory in the Yaml)host key, you would add the port key, like this: port: "127.0.0.1:5432:5432" — This way, you’re linking the host server’s internal / localhost 5432 port (127.0.0.1:5432) to the Postgres’ Docker image’s own 5432 port. This way, as far as the server is concerned, the Postgres server is only running on localhost:5432, and not exposed to the internet (which would be achieved by writing only 5432:5432 instead)5433 or 5434, while still routing it to the Docker image 5432 port, like this: 127.0.0.1:5433:5432. You’d do this just to avoid multiple containers colliding in port 5432 in the host machine – but in your Rails deploy.yml file, in the env section, you’ll still define the DB_PORT as 5432, because the communication is happening inside the Docker network through the servicename-postgres name (Inside the Docker network, containers always use the internal port, 5432)env variables to hook up the database to the Rails app:POSTGRES_USER and POSTGRES_DB should contain your app name as Rails usually uses it when creating databases. They usually look like appname_development, appname_production, etc. (if you’re unsure, check out what your development database name is)DB_HOST gets set to a special name that Kamal builds for you. It’s the name that your database Docker container will get in production. Let me explain. The name has two parts: servicename and postgres. Let’s start with servicename: in Kamal’s deploy.rb file, in the first few lines, you define a service name. I usually set it to the same name as my app (appname), but it may be different. This name, whatever it is, let’s call it servicename, then gets used to name the Docker containers running in the production machine. In our case, we’re defining a PostgreSQL accessory named postgres, so the Docker container that will run in production will be named servicename-postgres. If you name your service or PostgreSQL accessory differently, you’ll need to adjust this.DB_PORT gets set to the server’s localhost port hooked up to the postgres Docker container’s port. Remember when I said you can use port 5433, 5434 or any other port instead of the default 5432 if you want to run multiple PostgreSQL containers in the same machine? This is where you need to also modify it.POSTGRES_PASSWORD gets passed here as env variable, but it’s defined and set in the .kamal/secrets file. You need to add it like POSTGRES_PASSWORD=$(cat config/db.key) or read its value from a password manager like Bitwarden, as discussed before. If you decide to save it to disk, make sure to add the config/db.key file to your .gitignore so you don’t commit it to GitHub, and know that it’s less secure than storing it in a password manager like 1password or Bitwarden.data:/var/lib/postgresql/dataTo finish, we need to actually use all these environment variables by linking them to the Rails’ app database config in config/database.yml:
# config/database.yml
production:
primary: &primary_production
<<: *default
username: <%= ENV["POSTGRES_USER"] %>
database: <%= ENV["POSTGRES_DB"] %>
password: <%= ENV["POSTGRES_PASSWORD"] %>
host: <%= ENV["DB_HOST"] %>
port: <%= ENV["DB_PORT"] %>
Of course, instead of deploying a PostgreSQL accessory with your Kamal app, you can also use a managed database, like the Digital Ocean PostgreSQL hosting, AWS RDS, or just host a PostgreSQL server yourself (as I outlined here) – in that case, just set the corresponding database URL and credentials in the .kamal/secrets, config/deploy.yml, and config/database.yml files, and do not define a Postgres accessory in Kamal’s deploy config.
If you’re using Cloudflare, you’ll need to set up forward_headers: true in the proxy setcion, like this:
proxy:
ssl: true
host: example.com
forward_headers: true
If you don’t do this, you won’t see the real requesters’ IP addresses and all you’ll see will be Cloudflare’s own IP addresses, especially if you’re using the cloudflare-rails gem, as described in this issue.
Sometimes you don’t want your own development machine to build the Docker images that you’ll use to deploy your Rails app. Why? Because you want a beefy machine to build your images faster and reduce deployment time, or simply because your development machine can’t build for the server’s target architecture.
Setting up a remote machine to build your Docker images is fairly simple. Just use the same hardening script we used to set up the server Docker host machine, and then set up the remote machine in Kamal’s deploy.yml file, like this:
# config/deploy.yml builder: arch: amd64 local: false remote: ssh://[email protected] args: RUBY_VERSION: 3.3.5 secrets: - AWS_ECR_PASSWORD - RAILS_MASTER_KEY
You don’t need to configure the remote build server’s Docker daemon for remote access. This means you don’t need to configure Docker in any specific way to expose Docker endpoints publicly, whether authenticated or not. That’s exactly what I got wrong, and how I got hacked. Just use the exact same hardening script as your Docker host server, and Kamal will just connect to the remote build server using just ssh – no Docker endpoints needed.
Make sure to specify all the correct stuff in deploy.yml:
amd64 is the target server architecturessh://[email protected] is the SSH access to the builder server, where docker is the user Kamal will use to log in via ssh to the machine (if you configured your server differently, it may be ubuntu, root or something else), and y.y.y.y is the IP address of the remote build server. Needless to say, your local development computer has to have the right ssh key configured and loaded to make the SSH connection to the remote build server.AWS_ECR_PASSWORD and RAILS_MASTER_KEY are environment variables defined in the .kamal/secrets file, as explained above, that will get passed to the remote build server and built Docker image, like the AWS ECR token for pushing the Docker image to the container registryTo deploy our Rails app with Kamal, we first need to deploy any accessories, like the PostgreSQL Docker image we’ve just configured, or else the Rails app will fail to boot because the database will not be found.
To deploy all Kamal accessories, run:
If you only want to deploy the PostgreSQL accessory, named postgres in our case, run:
kamal accessory boot postgres
If for whatever reason an accessory stops working and you need to reboot it, run:
kamal accessory reboot postgres
And if you want to remove the accessory to install it anew or because you no longer need it:
kamal accessory remove postgres
To verify all is working good, you can ssh into the server and run docker ps, it will list your Docker containers running in the host machine. If you only booted the Postgres accessory, it should show:
postgres:15 image running with name servicename-postgresYou can now deploy the full Rails app with Kamal by running:
If everything went right, you should now see 3 containers running in the Docker host server:
postgres:15 image running with name servicename-postgres (already deployed above with kamal accessory boot postgres) appname-web-0f123, where 0f123 is a long hash identifying your app versionbasecamp/kamal-proxy image running with name kamal-proxy, which is what routes traffic to your appAnd if you got here, you should already have a fully deployed Rails 8 app with a dockerized PostgreSQL database using Kamal!
When I wrote about how I exited the cloud a few months ago, one of the top objections I got was database backups. Many people were happy to pay the premium of managed databases like RDS just because they have automated backups. The argument was along the lines of: “but how do you recover from disasters if you’re off the cloud?”, implying instance snapshots are not good enough because they’d also go down in the event the datacenter goes down; or “how would you do offsite DB backups if you’re now off the cloud?”
The answer is: this is now extremely simple to do with Kamal! Almost trivial if you have everything else configured. Just add an accessory to back up your database!
You can have your Postgres Rails database backed up to any S3-compatible storage, so the backups are off your instance, and thus suitable for disaster recovery.
You can back up hourly, every minute, every night, every week – up to you and your needs.
To do this we’ll use the kartoza/pg-backup Docker image.
Just add the accessory to deploy.yml, after the PostgreSQL accessory:
pg-backup:
image: kartoza/pg-backup:latest
host: 1.2.3.4 # Must run on the same host IP as the postgres accessory
env:
clear:
# Run backups every night
CRON_SCHEDULE: "@daily"
REMOVE_BEFORE: 30 # Only keep the n most recent days of backups
STORAGE_BACKEND: S3
SSL_SECURE: "True"
POSTGRES_MAJOR_VERSION: 15 # Match your postgres version
secret: # These keys are looked up in .kamal/secrets
- POSTGRES_USER
- POSTGRES_PASS
- POSTGRES_HOST
- POSTGRES_PORT
- ACCESS_KEY_ID
- SECRET_ACCESS_KEY
- BUCKET
- HOST_BUCKET
- HOST_BASE
- DEFAULT_REGION
Make sure to:
host IP to the same IP you’re running the Postgres accessory on.POSTGRES_MAJOR_VERSION to the same Postgres version you’re using in the Kamal accessory.CRON_SCHEDULE to your needs. Do something like "0 2 * * *" if you want to back up every night at 2am (check out this great website for all available cron options) You can also use cron shorthands like @hourly, @daily, @midnight, etc..kamal/secrets file. Especially the S3 connection credentials: ACCESS_KEY_ID, SECRET_ACCESS_KEY, BUCKET, HOST_BUCKET, HOST_BASE, DEFAULT_REGIONPOSTGRES_USER, POSTGRES_PASS, POSTGRES_HOST, POSTGRES_PORT (for example, the Postgres accessory expects POSTGRES_PASSWORD but pg-backup expects POSTGRES_PASS, so you would just make an alias in the secrets file, like: POSTGRES_PASS=$POSTGRES_PASSWORD)If you just want a working .kamal/secrets file, you can just use mine, it will work out with pg-backup, provided you edit it with your own credentials and names, of course:
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
APP_NAME_BASE="yourappname" # Single source of truth for the base name, must be the same as your `service: ` name in deploy.yml
# Example of extracting secrets from 1password (or another compatible pw manager)
# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS})
# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS})
# Use a GITHUB_TOKEN if private repositories are needed for the image
# GITHUB_TOKEN=$(gh config get -h github.com oauth_token)
# Grab the registry password from ENV
# KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
# Improve security by using a password manager. Never check config/master.key into git!
RAILS_MASTER_KEY=$(cat config/master.key)
# Pasword to our AWS ECR container registry to push Docker images to
AWS_ECR_PASSWORD=$(aws ecr get-login-password --region us-east-1 --profile default)
# Shared Database Credentials
# Used by Rails app (as POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB)
# Used by postgres accessory for initialization (as POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB)
# Used by pg-backup accessory (as POSTGRES_USER, POSTGRES_PASS)
DB_USERNAME_LITERAL=$APP_NAME_BASE
DB_PRIMARY_PASSWORD=$(cat config/db.key)
# Variables for the Rails application and PostgreSQL container initialization
POSTGRES_USER=$DB_USERNAME_LITERAL
POSTGRES_PASSWORD=$DB_PRIMARY_PASSWORD
POSTGRES_DB="${APP_NAME_BASE}_production" # Database name derived from APP_NAME_BASE
# Define the database host string once
DB_HOST_LITERAL="${APP_NAME_BASE}-postgres"
# Variables for the Rails application
# The Rails app will look for an environment variable named DB_HOST
DB_HOST=$DB_HOST_LITERAL
# Variables for pg-backup accessory
# kartoza/pg-backup expects POSTGRES_PASS for the password
POSTGRES_PASS=$DB_PRIMARY_PASSWORD
# kartoza/pg-backup expects POSTGRES_HOST for the hostname
POSTGRES_HOST=$DB_HOST_LITERAL # Use the same literal definition
# pg-backup connects to the postgres accessory's internal port
POSTGRES_PORT="5432"
# Backblaze B2 (S3-compatible storage) secrets for DB backups
ACCESS_KEY_ID=$(cat config/backups/s3_access_key.key)
SECRET_ACCESS_KEY=$(cat config/backups/s3_secret_access_key.key)
# Define the actual root B2 bucket name (e.g., what you see in your B2 console)
B2_ROOT_BUCKET_NAME=$(cat config/backups/s3_bucket.key)
PROJECT_BACKUP_PREFIX=$APP_NAME_BASE # Or your Kamal service name: "$KAMAL_SERVICE"
# kartoza/pg-backup expects BUCKET to be the full path in S3 where backups should go
BUCKET="${B2_ROOT_BUCKET_NAME}/${PROJECT_BACKUP_PREFIX}"
B2_DEFAULT_REGION=$(cat config/backups/s3_region.key) # Internal variable for constructing HOST_BUCKET
DEFAULT_REGION=$B2_DEFAULT_REGION # kartoza/pg-backup expects DEFAULT_REGION
HOST_BUCKET="s3.${B2_DEFAULT_REGION}.backblazeb2.com" # kartoza/pg-backup expects HOST_BUCKET
HOST_BASE=$HOST_BUCKET # kartoza/pg-backup expects HOST_BASE
I use Backblaze B2 for my backups storage, but you can use any S3-compatible service like Cloudflare R2, Hetzner Object Storage, or AWS S3 itself! Just update the secrets file with your service-specific config.
Tip: make sure you extract your backup S3 secrets from 1password (or another Kamal-compatible password manager!) In my config above, for simplicity, I’ve chosen to put them in files à la config/db.key, so, at the very least, DO NOT COMMIT the secrets to git! Add the /config/backups/* folder to your .gitignore
After all is configured, just boot the accessory:
kamal accessory boot pg-backup
To verify all is working, open up a shell terminal into the pg-backup container:
kamal accessory exec pg-backup --interactive --reuse "bash"
Then, inside the pg-backup container, you can check the crontab job is correctly configured:
root@abc123:/backup-scripts# crontab -l # Run the backups at 11pm each night 0 2 * * * /backup-scripts/backups.sh > /var/log/cron.out 2>&1 # We need a blank line here for it to be a valid cron file
And see the logs of the backup job:
root@abc123:/backup-scripts# cat /var/log/cron.out /your-bucket-name/yourappname/2025/May ~ Bucket 'your-bucket-name/yourappname' exists. upload: '<stdin>' -> 's3://your-bucket-name/yourappname/globals.sql' (0 bytes in 0.4 seconds, -1.00 B/s) [1 of 1] WARNING: Module python-magic is not available. Guessing MIME types based on file extensions. upload: '/your-bucket-name/yourappname/2025/May/PG_yourappname_production.20-May-2025.dmp.gz' -> 's3://your-bucket-name/yourappname/2025/May/PG_yourappname_production.20-May-2025.dmp.gz' (460363 bytes in 0.0 seconds, 19.12 MB/s) [1 of 1] Done. Uploaded 460363 bytes in 1.0 seconds, 449.57 KB/s.
If all is good, you’ll see an output like the above after the backup job has been triggered. If something fails (bucket not found, bad credentials, etc.) you’ll see the specific errors there.
This way, you can set up database backups easily. You’ll now be automatically backing up your Rails Postgres database regularly, in a location different than your main production database, to ensure no data loss or minimize data loss in the case of a failure or catastrophe. An easy, fast, cost-effective recovery plan set up in minutes thanks to Kamal!
P.S.: Thanks a lot to Stephen in the comments for the tip re: using Docker images for automatic database backups leveraging Kamal accessories; and to @Jankeesvw for helping me find the pg-backup Docker image and providing me with a working example!
Now that you have your Rails app up and running, you’ll probably need to operate it in production. Some things you’ll want to do are: check data and change data in the production database, ssh into the Rails Docker image to run Linux commands, and see production logs. Kamal 2 provides easy shortcuts for each of these tasks. Let’s go one by one:
This is an easy one. Just run:
And you should get an interactive Rails console hooked up to your production database.
Sometimes you want to execute some SQL directly from a psql client. In that case, we need to access the production postgres accesory container and execute an interactive psql console inside it. For that, run:
This is a shortcut for kamal app exec --interactive --reuse "bin/rails dbconsole", but you can also launch an interactive psql console for your accessory container with this long-form command:
kamal accessory exec postgres -i "psql -h servicename-postgres -p 5432 -U appname appname_production"
This will prompt you to input your production database password. After that, you’ll get a fully interactive psql console where you can run your usual PostgreSQL commands and SQL queries.
To open up an interactive Linux console inside the Docker image running your Rails app, execute:
Which is just an alias for the longer command:
And you’ll get an interactive shell where you’ll be able to run any Linux command. Just remember that any change you make to the Linux system executing your Rails app won’t be preserved across deployments – if you want that, you should make changes in the Rails app’s Dockerfile instead so the Docker image gets built with your new instructions.
Just run:
kamal accessory exec postgres --interactive --reuse "bash"
And you’ll get an interactive console to the Postgresql container.
If you want the equivalent of navigating to the logs folder and running tail -f production.log, just run the handy Kamal alias:
And you’ll get a stream of changes in the production log file.
This may be obvious for those already familiar with Docker, but it was not for me – I’ve been using Capistrano for years and I’m still getting used to Docker containers.
If you’re already ssh‘d into your Docker host server, you can launch a shell console inside any container with:
docker exec -it -u DOCKER_CONTAINER_LINUX_USERNAME DOCKER_CONTAINER_NAME /bin/bash
Where DOCKER_CONTAINER_NAME is the name the container has when you run docker ps, and DOCKER_CONTAINER_LINUX_USERNAME is the Linux user inside the container that will be used to launch the console.
I recently finished migrating all my legacy Capistrano apps to Kamal. I learned a bunch in the process. It’s pretty straightforward, here’s what I do:
rails new demoapp --database=postgresql.kamal, .dockerignore, config/deploy.yml or Dockerfile; and deleting Capistrano files like config/deploy.rb, config/deploy/production.rb, or Capfile/up route ready in your routes.rb file: get "up" => "rails/health#show", as: :rails_health_checkconfig/environments/production.rb, some settings may give you errors while deploying the new Kamal app, specially make sure you have config.assume_ssl = true and config.force_ssl = true — Again, I suggest checking line by line against the brand new Rails 8 project; in case of doubt, favor the new Rails 8 approachOnce you have removed all Capistrano files, added all new Kamal files, and updated the Gemfile, production.rb and all other relevant files for Kamal to work, and your app now correctly deploys with Kamal, you can dump and import the database:
ssh root@capistrano_server_ippg_dump yourapp_production > /tmp/yourapp_production.sqlgzip /tmp/yourapp_production.sql (optional, but gzipping the dump makes the data transfer faster!)scp root@capistrano_server_ip:/tmp/yourapp_production.sql.gz .rsync -e "ssh -i ~/.ssh/your-docker-host-private-ssh-key" yourapp_production.sql.gz docker@docker_host_machine_ip:/tmp/ (you can also use scp if you want, I found it didn’t work with me under my current host machine config)ssh docker@docker_host_machine_ipdocker ps to list all containers and see the name of your app’s postgres accessory container name, say it’s yourapp-postgres/tmp directory into the Postgres Docker container (also in the container’s /tmp directory): docker cp /tmp/yourapp_production.sql.gz yourapp-postgres:/tmp/docker exec -it yourapp-postgres bash/tmp dir: cd /tmpgunzip yourapp_production.sql.gzpsql -U yourapp yourapp_production < yourapp_production.sqlThat’s it! It looks like a lot of steps but it’s actually pretty straightforward if you’re careful. Your mileage may vary of course with your own app details, but these are the general guidelines to migrate a Capistrano Rails app into Kamal!
If your Rails app is using the Devise gem, chances are you’ll get a cable-related error when deploying it with Kamal. The error looks something like:
Firefox can’t establish a connection to the server at wss://example.com/cable. wss: ActionController::RoutingError (No route matches "cable")
This error stumped me for a good while, but the solution is as easy as setting this in your config/initializers/devise.rb file:
# config/initializers/devise.rb config.reload_routes = false
The error is well documented in this GitHub issue.
Now that your Rails app is running in production using Kamal, there’s a few things you could look into:
If you have good ideas on how to do any of the above, please do write a comment below! I do not know what’s the best way of doing many of these things and could use some help.
P.S.: Follow me on X! You can also read other stories I've written, and subscribe below to get an alert when I publish a new post:
No spam ever, unsubscribe with one single click.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。