Agent Skill
2/7/2026

kamal-coder

This skill guides deploying Rails applications with Kamal. Use when configuring deploy.yml, setting up accessories, managing secrets, or preparing servers for container deployment.

M
majesticlabs
24GitHub Stars
1Views
npx skills add majesticlabs-dev/majestic-marketplace

SKILL.md

Namekamal-coder
DescriptionThis skill guides deploying Rails applications with Kamal. Use when configuring deploy.yml, setting up accessories, managing secrets, or preparing servers for container deployment.

name: kamal-coder description: This skill guides deploying Rails applications with Kamal 2. Use when configuring deploy.yml, setting up kamal-proxy, managing secrets, accessories, or preparing servers for container deployment. allowed-tools: Read Write Edit Grep Glob Bash

Kamal 2 Coder

Servers need Docker, SSH access, and ports 22/80/443 open. Provision with Ansible or cloud-init.

Configuration: config/deploy.yml

Minimal Setup

service: myapp
image: myapp

servers:
  web:
    - 203.0.113.10

proxy:
  ssl: true
  host: myapp.com

registry:
  username: username
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  clear:
    RAILS_ENV: production
    RAILS_LOG_TO_STDOUT: "true"
  secret:
    - RAILS_MASTER_KEY

Multi-Role Setup (Web + Job Worker)

service: myapp
image: myapp

servers:
  web:
    - 203.0.113.10
  job:
    hosts:
      - 203.0.113.10
    cmd: bin/jobs start

proxy:
  ssl: true
  host: myapp.com

registry:
  username: username
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  clear:
    RAILS_ENV: production
    SOLID_QUEUE_IN_PUMA: false
  secret:
    - RAILS_MASTER_KEY

Job worker notes:

  • cmd: bin/jobs start runs Solid Queue in a separate container
  • Set SOLID_QUEUE_IN_PUMA: false to disable in-process queue
  • Job role has no proxy — only web role serves HTTP traffic

With Local Registry

Eliminates Docker Hub dependency, rate limits, and external costs:

registry:
  server: localhost:5555
  username: ignored
  password:
    - KAMAL_REGISTRY_PASSWORD

Deploy the registry as an accessory:

accessories:
  registry:
    image: registry:2
    host: 203.0.113.10
    port: "5555:5000"
    volumes:
      - registry_data:/var/lib/registry

With Accessories

accessories:
  db:
    image: postgres:16
    host: 203.0.113.10
    port: 5432
    env:
      clear:
        POSTGRES_DB: myapp_production
      secret:
        - POSTGRES_PASSWORD
    directories:
      - data:/var/lib/postgresql/data
    options:
      shm-size: 256m

  redis:
    image: redis:7-alpine
    host: 203.0.113.10
    port: 6379
    directories:
      - data:/data
    cmd: redis-server --appendonly yes

Docker Volumes for Persistence

For SQLite + ActiveStorage apps, mount a named volume:

servers:
  web:
    hosts:
      - 203.0.113.10
    volumes:
      - myapp_storage:/rails/storage
    labels:
      docker-volume-backup.stop-during-backup: "true"
  job:
    hosts:
      - 203.0.113.10
    cmd: bin/jobs start
    volumes:
      - myapp_storage:/rails/storage

Both web and job containers share the same volume for database access.

Proxy Configuration (kamal-proxy)

Kamal 2 uses kamal-proxy (not Traefik). It handles SSL termination, routing, and zero-downtime deploys.

Basic SSL

proxy:
  ssl: true
  host: myapp.com

Automatic Let's Encrypt certificate provisioning — no manual cert management.

Custom Port

proxy:
  ssl: true
  host: myapp.com
  app_port: 3000

Multiple Hosts

proxy:
  ssl: true
  hosts:
    - myapp.com
    - www.myapp.com

Health Check

proxy:
  ssl: true
  host: myapp.com
  healthcheck:
    path: /up
    interval: 3
    timeout: 3

Response Timeout

proxy:
  ssl: true
  host: myapp.com
  response_timeout: 30

Secrets: .kamal/secrets

Kamal reads secrets from .kamal/secrets (git-ignored).

With 1Password CLI

KAMAL_REGISTRY_PASSWORD=$(op read "op://Infrastructure/DockerHub/password")
RAILS_MASTER_KEY=$(op read "op://MyApp/production/master_key")
DATABASE_URL=$(op read "op://MyApp/production/database_url")

With Environment Variables

KAMAL_REGISTRY_PASSWORD=$DOCKERHUB_TOKEN
RAILS_MASTER_KEY=$RAILS_MASTER_KEY
DATABASE_URL=$DATABASE_URL

Multi-Environment

# config/deploy.yml — base config
service: myapp

# config/deploy.staging.yml — overrides
service: myapp-staging
servers:
  web:
    - 203.0.113.20
proxy:
  host: staging.myapp.com
# .kamal/secrets.staging
RAILS_MASTER_KEY=$(op read "op://MyApp/staging/master_key")

Deploy with: kamal deploy -d staging

Common Commands

First Deployment

# One-time: installs kamal-proxy, pushes image, deploys
kamal setup

# Subsequent: builds, pushes, rolling restart
kamal deploy

Regular Operations

kamal deploy                    # Deploy latest
kamal deploy --version=abc123   # Deploy specific version
kamal deploy -d staging         # Deploy to staging
kamal redeploy                  # Redeploy without building

Rollback

kamal app containers            # List available versions
kamal rollback <version>        # Rollback to specific version

Debugging

kamal app exec --interactive bash              # Shell into container
kamal app logs -f                              # Tail logs
kamal app exec --interactive "bin/rails console"  # Rails console
kamal app exec "bin/rails db:migrate"          # Run migrations

Accessories

kamal accessory boot all        # Start all accessories
kamal accessory reboot db       # Restart specific accessory
kamal accessory exec db --interactive "psql -U postgres"
kamal accessory logs litestream  # View accessory logs

Builder Configuration

Native Builds

builder:
  arch: amd64

Multi-Architecture

builder:
  multiarch: true

Remote Builder

builder:
  remote:
    arch: amd64
    host: ssh://builder@build-server

Build Arguments

builder:
  args:
    RUBY_VERSION: "3.3.0"

Hooks

Pre-Deploy

# .kamal/hooks/pre-deploy
#!/bin/sh
echo "Running pre-deploy checks..."

Post-Deploy

# .kamal/hooks/post-deploy
#!/bin/sh
echo "Deploy complete: $(date)"
curl -s https://notify.example.com/deploy

Provisioning Workflow

Ansible + Kamal Pipeline

# 1. Ansible: Configure server
ansible-playbook -i hosts.ini playbook.yml

# 2. Kamal: Bootstrap and deploy
kamal setup

What Ansible Should Configure

Based on kamal-ansible-manager:

TaskPurpose
Install DockerContainer runtime
Configure fail2banSSH intrusion prevention
Setup UFWFirewall (22, 80, 443)
Enable NTPTime synchronization
Create swapMemory overflow protection
Harden SSHDisable password auth, root login
Unattended upgradesAutomatic security patches

Litestream Backup Accessory

For SQLite apps, add Litestream as an accessory (see litestream-coder for full config):

accessories:
  litestream:
    image: litestream/litestream:0.3
    host: 203.0.113.10
    cmd: replicate
    volumes:
      - myapp_storage:/rails/storage:ro
    files:
      - config/litestream.yml:/etc/litestream.yml
    env:
      secret:
        - LITESTREAM_ACCESS_KEY_ID
        - LITESTREAM_SECRET_ACCESS_KEY

Mount storage as read-only (:ro) — Litestream only reads WAL files.

Directory Structure

myapp/
├── config/
│   ├── deploy.yml           # Main Kamal config
│   └── deploy.staging.yml   # Staging overrides
├── .kamal/
│   ├── secrets              # Production secrets (git-ignored)
│   ├── secrets.staging      # Staging secrets (git-ignored)
│   └── hooks/
│       ├── pre-deploy
│       └── post-deploy
├── Dockerfile               # Application container
└── docker-entrypoint.sh     # Entrypoint script

Troubleshooting

IssueCauseFix
Connection refusedDocker not runningkamal setup or check Docker service
Permission deniedSSH key not authorizedCheck server's authorized_keys
Health check failingApp not startingCheck kamal app logs
Registry auth failedWrong credentialsVerify .kamal/secrets
502 Bad GatewayContainer not healthyIncrease healthcheck timeout
SSL cert not issuedDNS not pointing to serverVerify DNS A record
Asset 404 after deployVolume not mountedCheck volumes: in deploy.yml

References

Skills Info
Original Name:kamal-coderAuthor:majesticlabs