One Shot deploys with [Kamal](https://kamal-deploy.org): 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

```bash
bin/kamal setup
```

This builds the image and boots the app for the first time. Verify it worked:

```bash
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](/docs/deployment/edge-and-dns-setup)):

```bash
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

```bash
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

```bash
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](/docs/deployment/first-deploy-checklist).
