Deploying with Kamal
One-command Docker deploys with no PaaS, and the deploy topology.
One Shot deploys with Kamal: one command, any Docker host you control, no proprietary platform-as-a-service subscription in the loop. This site's own production deployment, shiponeshot.com, runs this exact setup on a small server it shares with another unrelated app, each fully independent with its own service name, image, volume, and port.
Why Kamal, and what that trades away
Kamal builds a Docker image, ships it to any server with Docker installed, and runs it there, handling zero-downtime restarts and basic proxying itself. Compared to a managed PaaS, you give up one-click scaling and a managed database in exchange for running on whatever server you already have (or a $5 to $20/month VPS), with no per-seat or per-request platform fee layered on top of your infrastructure cost. This fits a kit built around SQLite and Solid Queue: the whole app, including its background jobs and its database, lives inside one container, on one machine, with no separate services to provision.
First deploy
bin/kamal setup
This builds the image and boots the app for the first time. Verify it worked:
bin/kamal logs -f
Then confirm the app answers on its own, and through whatever's in front of it (see Edge and DNS Setup):
curl -sI http://127.0.0.1:<your-port>/up # on the server itself
curl -sI https://yourdomain.com/up # through your public edge
Every deploy after the first
bin/kamal deploy
A pre-app-boot hook removes the previous container before starting the new one, since the host
port is fixed; expect a few seconds of downtime on each deploy, not a rolling zero-downtime
handoff. For an app with Solid Queue running in-process, this also means jobs pause briefly during
that window rather than continuing on a separate worker process.
Useful commands
bin/kamal logs -f # tail production logs
bin/kamal console # a Rails console on the server
bin/kamal app exec "bin/rails db:migrate" # run a one-off command
CI/CD
A GitHub Actions workflow can mirror bin/check exactly, running the same bin/* wrappers
(Brakeman, bundler-audit, gitleaks, importmap audit, RuboCop, RSpec) as separate parallel jobs, so
a green bin/check locally predicts a green pipeline. Because Kamal deploys need direct access to
your server, the deploy job itself typically runs on a self-hosted runner with network access to
that server, rather than GitHub's own hosted runners, and triggers kamal deploy automatically on
a green main.
Next
Before your first deploy, work through the checklist: First Deploy Checklist.