返回 Skill 列表
extension
分类: 开发与工程无需 API Key

kamal-coder

此技能指导使用Kamal部署Rails应用程序。在配置deploy.yml、设置配件、管理密钥或准备容器部署的服务器时使用。

person作者: jakexiaohubgithub

Kamal 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: username/myapp

servers:
  web:
    hosts:
      - 192.168.1.1
    labels:
      traefik.http.routers.myapp.rule: Host(`myapp.com`)

registry:
  username: username
  password:
    - KAMAL_REGISTRY_PASSWORD

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

Multi-Role Setup

service: myapp
image: username/myapp

servers:
  web:
    hosts:
      - 192.168.1.1
      - 192.168.1.2
    labels:
      traefik.http.routers.myapp.rule: Host(`myapp.com`)
  worker:
    hosts:
      - 192.168.1.3
    cmd: bundle exec sidekiq
    traefik: false  # No HTTP traffic

registry:
  username: username
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  clear:
    RAILS_ENV: production
  secret:
    - RAILS_MASTER_KEY
    - DATABASE_URL
    - REDIS_URL

With Accessories (Databases, Redis)

service: myapp
image: username/myapp

servers:
  web:
    hosts:
      - 192.168.1.1

accessories:
  db:
    image: postgres:16
    host: 192.168.1.1
    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: 192.168.1.1
    port: 6379
    directories:
      - data:/data
    cmd: redis-server --appendonly yes

Secrets: .kamal/secrets

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

With 1Password CLI

# .kamal/secrets
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")
POSTGRES_PASSWORD=$(op read "op://MyApp/production-db/password")

With Environment Variables

# .kamal/secrets
KAMAL_REGISTRY_PASSWORD=$DOCKERHUB_TOKEN
RAILS_MASTER_KEY=$RAILS_MASTER_KEY
DATABASE_URL=$DATABASE_URL

Multi-Environment

# config/deploy.yml
<% if ENV["KAMAL_DESTINATION"] == "staging" %>
service: myapp-staging
<% else %>
service: myapp
<% end %>
# .kamal/secrets.staging
RAILS_MASTER_KEY=$(op read "op://MyApp/staging/master_key")

Traefik Configuration

SSL with Let's Encrypt

traefik:
  options:
    publish:
      - "443:443"
    volume:
      - /letsencrypt:/letsencrypt
  args:
    entryPoints.web.address: ":80"
    entryPoints.websecure.address: ":443"
    entryPoints.web.http.redirections.entryPoint.to: websecure
    entryPoints.web.http.redirections.entryPoint.scheme: https
    certificatesResolvers.letsencrypt.acme.email: admin@myapp.com
    certificatesResolvers.letsencrypt.acme.storage: /letsencrypt/acme.json
    certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web

servers:
  web:
    hosts:
      - 192.168.1.1
    labels:
      traefik.http.routers.myapp.rule: Host(`myapp.com`)
      traefik.http.routers.myapp.entrypoints: websecure
      traefik.http.routers.myapp.tls.certresolver: letsencrypt

Health Checks

healthcheck:
  path: /up
  port: 3000
  interval: 10s
  max_attempts: 30

Common Commands

First Deployment

# Bootstrap server (installs Docker, creates directories)
kamal server bootstrap

# Full setup (push config, start traefik, deploy app)
kamal setup

Regular Deployment

# Deploy latest
kamal deploy

# Deploy specific version
kamal deploy --version=abc123

# Deploy to staging
kamal deploy -d staging

Rollback

# List available versions
kamal app containers

# Rollback to previous
kamal rollback

Debugging

# SSH into container
kamal app exec --interactive bash

# View logs
kamal app logs -f

# Rails console
kamal app exec --interactive "bin/rails console"

Accessories

# Start all accessories
kamal accessory boot all

# Restart specific accessory
kamal accessory reboot db

# Exec into accessory
kamal accessory exec db --interactive psql -U postgres

Provisioning Workflow

Terraform + Ansible + Kamal Pipeline

# infra/bin/provision
#!/usr/bin/env bash
set -euo pipefail

# 1. Terraform: Create infrastructure
cd infra && tofu apply

# 2. Ansible: Configure server
SERVER_IP=$(tofu output -raw server_ip)
cd ansible
echo "[web]\n$SERVER_IP ansible_user=root" > hosts.ini
ansible-playbook -i hosts.ini playbook.yml

# 3. Kamal: Bootstrap containers
cd ../..
bundle exec kamal server bootstrap

What Ansible Should Configure

Based on kamal-ansible-manager:

| Task | Purpose | |------|---------| | Install Docker | Container runtime | | Configure fail2ban | SSH intrusion prevention | | Setup UFW | Firewall (22, 80, 443) | | Enable NTP | Time synchronization | | Create swap | Memory overflow protection | | Harden SSH | Disable password auth, root login | | Kernel tuning | swappiness, somaxconn |

Builder Configuration

Native ARM64 Builds (Hetzner CAX)

builder:
  arch: arm64
  # OR for multi-arch:
  # multiarch: true

Remote Builder

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

Hooks

Pre-Deploy

# .kamal/hooks/pre-deploy
#!/bin/sh
echo "Running pre-deploy tasks..."
bundle exec rails assets:precompile

Post-Deploy

# .kamal/hooks/post-deploy
#!/bin/sh
echo "Running migrations..."
kamal app exec "bin/rails db:migrate"

Directory Structure

myapp/
├── config/
│   └── deploy.yml        # Main Kamal config
├── .kamal/
│   ├── secrets           # Secret values (git-ignored)
│   ├── secrets.staging   # Staging secrets (git-ignored)
│   └── hooks/
│       ├── pre-deploy
│       └── post-deploy
└── Dockerfile            # Application container

Troubleshooting

| Issue | Cause | Fix | |-------|-------|-----| | Connection refused | Docker not running | kamal server bootstrap | | Permission denied | SSH key not authorized | Check server's authorized_keys | | Health check failing | App not starting | Check kamal app logs | | Registry auth failed | Wrong credentials | Verify .kamal/secrets | | Traefik 502 | Container not healthy | Increase max_attempts |