# Kamal Deployment

> Deploy containerized applications (especially Rails) to VPS using Kamal 2. Covers deploy.yml configuration, accessories (PostgreSQL, Redis, Sidekiq), SSL/TLS, secrets management, CI/CD with GitHub Actions, database backups, server hardening, debugging, and scaling. Use when setting up Kamal, configuring deployments, troubleshooting deploy issues, or managing production infrastructure with Kamal.

- Skill: `faqndo97/kamal-deployment` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add faqndo97/kamal-deployment`
- Raw SKILL.md: https://api.skillmd.com/api/skills/faqndo97/kamal-deployment/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: faqndo97 (https://skillmd.com/u/faqndo97)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/faqndo97/kamal-deployment

---


# Kamal 2 Deployment

Kamal is a deployment tool by 37signals that deploys containerized applications to any Linux server using Docker, SSH, and Kamal Proxy. It offers zero-downtime deploys, rolling restarts, automatic SSL/TLS certificates, and auxiliary services management.

## Quick Start

```bash
# Install
gem install kamal

# Initialize in your project
kamal init
# Creates: config/deploy.yml, .kamal/secrets, .kamal/hooks/

# First deploy (bootstraps servers + deploys)
kamal setup

# Subsequent deploys
kamal deploy
```

## Key Concepts

- **Service**: Your application name, used to uniquely configure containers
- **Server**: A virtual or physical host running your application image
- **Role**: A server group running the same command (e.g., `web`, `job`)
- **Accessory**: A long-running auxiliary service (database, Redis) with independent lifecycle
- **Kamal Proxy**: Reverse proxy for zero-downtime deploys and SSL termination on each server
- **Kamal 2 provides its own private network** called `kamal` - do NOT add a custom private network in your config

## Configuration Reference

For complete deploy.yml configuration, see [configuration.md](configuration.md).

## Workflows

### Initial Setup

1. Run `kamal init` to generate config files
2. Configure `config/deploy.yml` (see [configuration.md](configuration.md))
3. Set up `.kamal/secrets` with registry credentials and app secrets
4. Ensure your app has a Dockerfile exposing port 80 with a `/up` health check
5. Set `config.assume_ssl = true` and `config.force_ssl = true` in `production.rb`
6. Exclude `/up` from host authorization in Rails 7+:
   ```ruby
   config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
   ```
7. Run `kamal setup` for the first deployment

### Deploying Updates

```bash
kamal deploy                    # Full deploy (build + push + deploy)
kamal deploy --skip-push        # Deploy existing image from registry
kamal deploy --version=VERSION  # Deploy specific version
```

### Managing Accessories

```bash
kamal accessory boot all        # Boot all accessories
kamal accessory boot postgres   # Boot specific accessory
kamal accessory reboot redis    # Reboot an accessory
kamal accessory remove postgres # Remove an accessory
kamal accessory details postgres
kamal accessory logs postgres
```

### Maintenance & Debugging

```bash
# Logs
kamal app logs                  # Application logs
kamal app logs -f               # Follow logs
kamal app logs -r web           # Logs for specific role
kamal proxy logs                # Proxy logs

# Console access
kamal app exec -i 'bin/rails console'
kamal app exec -i --reuse bash  # Shell into running container
kamal app exec --primary "bin/rails about"

# Rollback
kamal app containers            # List available versions
kamal rollback [VERSION]         # Rollback to version

# Redeploy (skip bootstrap, proxy setup, pruning, registry login)
kamal redeploy

# Locks
kamal lock status               # Check deploy lock
kamal lock acquire -m "reason"  # Prevent deploys
kamal lock release              # Allow deploys again

# Server management
kamal server bootstrap          # Bootstrap servers with Docker
kamal details                   # Show details about all containers
kamal audit                     # Show audit log from servers
kamal prune                     # Prune old images and containers
kamal config                    # Show combined config (includes secrets!)
kamal docs [SECTION]            # Show Kamal configuration docs

# Cleanup
kamal remove                    # Remove everything from servers
```

### Global Flags

| Flag | Description |
|------|-------------|
| `-v, --verbose` | Detailed logging |
| `-q, --quiet` | Minimal logging |
| `--version=VERSION` | Run against specific app version |
| `-p, --primary` | Primary host only |
| `-h, --hosts=HOSTS` | Comma-separated hosts (supports wildcards) |
| `-r, --roles=ROLES` | Comma-separated roles (supports wildcards) |
| `-d, --destination` | Deployment destination |
| `-H, --skip-hooks` | Skip hook execution |

### Database Operations

```bash
# Run migrations via entrypoint (recommended)
# bin/docker-entrypoint handles db:prepare automatically

# Or via pre-deploy hook
kamal app exec -p -q -d $KAMAL_DESTINATION --version $KAMAL_VERSION "rails db:migrate"

# Database backup (with S3 backup accessory)
kamal accessory exec s3_backup "sh backup.sh"
kamal accessory exec s3_backup "sh restore.sh"
```

## Architecture Patterns

For complete examples of single-server and multi-server setups, see [examples.md](examples.md).

### Single Server (Rails + PostgreSQL + Redis + Sidekiq)

All services on one VPS with Let's Encrypt SSL. Best for small-to-medium apps.

### Multi-Server (Scaled)

Separate servers for web, job, database, and cache behind a load balancer on a private network. Best for high-traffic apps.

### Hooks

Custom scripts in `.kamal/hooks/` that run at deployment points. If a hook returns non-zero, the command aborts.

Available: `docker-setup`, `pre-connect`, `pre-build`, `pre-deploy`, `post-deploy`, `pre-app-boot`, `post-app-boot`, `pre-proxy-reboot`, `post-proxy-reboot`.

For details, see [configuration.md](configuration.md#hooks).

### Upgrading from Kamal 1

```bash
kamal upgrade              # In-place upgrade from Kamal 1 to 2
kamal upgrade --rolling    # Zero-downtime upgrade
kamal downgrade            # Reverse if needed
```

## Common Gotchas

- **Kamal 2 creates its own `kamal` network** - remove any custom private network from your config
- **Always set `config.assume_ssl = true`** in `production.rb` when using SSL
- **Do NOT use your domain name as the VM hostname** - it overrides `/etc/resolv.conf`
- **Docker port exposure bypasses UFW** - closing ports in UFW is not enough, Docker rules are higher in iptables
- **Short `deploy_timeout` causes failures** on underpowered servers - increase it if deploys fail
- **Asset bridging must be explicitly configured** with `asset_path` - it's not automatic
- **Accessories must be removed before moving** to a new destination
- **Kamal 2 doesn't support ERB** in `config/deploy.yml` (unlike Kamal 1)
- **Set `config.reload_routes = false`** in Devise initializer to fix ActionController::RoutingError
- **Enable `forward_headers: true`** in proxy config when behind Cloudflare to preserve real client IPs

## CI/CD

For GitHub Actions deployment workflows, see [cicd.md](cicd.md).

## Backups

For database backup strategies with S3, see [backups.md](backups.md).

## Server Hardening

For production server security setup, see [server-hardening.md](server-hardening.md).

## Logging & Monitoring

For Vector log aggregation and structured logging, see [logging.md](logging.md).

