I ran into a problem with Kamal. My .kamal/secrets file was full of API keys sitting in plaintext on my laptop. Anyone with access could read them all.
TLDR; Use and deploy to a CAX series starts at around 4 euro a month. I use the CX22 with 2 vCPUs and 4GB RAM. Enough for production.
# On your Hetzner server
apt update && apt install -y docker.io
# Copy your SSH key so Kamal can connect
ssh-copy-id root@your-server-ip
Your config/deploy.yml:
servers:
web:
hosts:
- runtime.yourdomain.com
proxy:
ssl: true
hosts:
- runtime.yourdomain.com
healthcheck:
path: /health/ready
registry:
server: docker.io
username: your-docker-user
password:
- KAMAL_REGISTRY_PASSWORD
You need a Console:
- Go to Secrets Manager > Store a new secret
- Select "Other type of secret"
- Switch to plaintext tab and paste your JSON
{
"DEEPGRAM_API_KEY": "your_deepgram_key",
"ASSEMBLY_AI_API_KEY": "your_assemblyai_key",
"REDIS_URL": "redis://:password@your-redis:6379",
"KAMAL_REGISTRY_PASSWORD": "your_docker_token"
}
- Name it
myapp/production/secrets
- Click Store
Pick a region close to your server. If your Hetzner box is in Germany, use eu-central-1 (Frankfurt). Keeps latency low and GDPR happy.
Step 3: IAM user for your laptop
Your laptop needs permission to read the secret during deploy.
- Go to IAM > Users > Create user
- Name it
kamal-deploy
- Uncheck console access (CLI only)
- Create a group called
secrets-managerwith the SecretsManagerReadWrite policy - Add an inline policy for batch reading:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:BatchGetSecretValue",
"secretsmanager:ListSecrets"
],
"Resource": "*"
}
]
}
- Add your user to the group
IAM policies can take a minute to propagate. If it fails at first, wait 30 seconds and try again.
Step 4: Configure AWS CLI
aws configure
# AWS Access Key ID: paste from IAM user
# AWS Secret Access Key: paste
# Default region name: eu-central-1
# Default output format: json
Test it:
aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text | head -c 50
You should see the start of your JSON.
Step 5: Format your .kamal/secrets file
This is where I got stuck. The --from flag wants one AWS secret per key. Having 20 separate secrets is annoying. Check the , the for keeping hosting affordable. Hope this saves you the same headaches I ran into. Now back to building.
SOCIAL SHARE CARD GENERATOR