Deploying
Services are deployed with Kamal, which builds Docker containers and rolls them out behind a Traefik reverse proxy with automatic SSL.
Deploy a service
bin/kamal py deploy
# or
make kamal ARGS="py deploy"
First-time setup for a new service:
bin/kamal py setup
bin/kamal py deploy
How it works
bin/kamalwraps itself inconfit sshto load the Terraform-generated deploy key- Kamal reads the service config from
config/deploy/<service>.yml - Secrets are injected from
.kamal/secrets, which callsconfit resolve --reveal - Kamal builds the Docker image (context:
web/, dockerfile:web/<service>/Dockerfile) - The image is pushed to Docker Hub and deployed with zero-downtime rolling updates
Service configs
Each service has a Kamal config at config/deploy/<service>.yml. These are ERB templates that call confit for dynamic values:
service: <%%= `confit resolve project.name`.strip %>-py
image: <%%= `confit resolve services.py.repo`.strip %>-py
servers:
web:
- <%%= `confit --set stage=production resolve credentials.server.ip`.strip %>
proxy:
ssl: true
host: <%%= `confit resolve services.py.domain`.strip %>
app_port: <%%= `confit resolve services.py.port`.strip %>
Environment variables are defined directly in the Kamal config — not in confit.toml.
Secrets
.kamal/secrets resolves secrets via confit and exports them for Kamal:
DOCKER_HUB_TOKEN=$(confit resolve --reveal credentials.cloud.docker_hub_token)
Reference these in the Kamal config under env.secret.
Adding a new service
- Add the service source under
web/<name>/with aDockerfile - Add a
[services.<name>]entry inconfit.tomlwithrepo,domain, andport - Create
config/deploy/<name>.ymlusing an existing config as a template - Run
make infra ARGS="apply"to create the DNS record (the subdomain is picked up automatically fromservices.<name>.domain) - Deploy:
bin/kamal <name> setup && bin/kamal <name> deploy
Available services
| Service | Domain | Port | Stack |
|---|---|---|---|
| py | py.<zone> |
8000 | Python (FastAPI) |
| rust | rust.<zone> |
3000 | Rust (Axum) |
| ts-web | ts.<zone> |
3000 | TypeScript (Next.js) |