Kamal plays wonderfully with Rails, but did you know it’s a general-purpose container tool? I recently set up Metabase with Kamal, and while there were minor bumps, the process went smoothly overall. Here’s a walkthrough of how I got it running.
For this deployment, I used a 2GB RAM Linode instance to handle the workload. After creating the machine:
Locally, I created a new project directory and initialized it with Kamal:
mkdir PROJECT
cd PROJECT
bundle init
bundle add kamal
kamal init
mkdir .kamal/env
touch .kamal/env/.gitkeep
touch .kamal/env/postgres_password
git init
echo '.kamal/env/*' >> .gitignore
git add .
git add .kamal/env/.gitkeep -f
git commit -m "initial commit"
touch Dockerfile
This setup establishes the project structure, secrets storage, and version control.
Here’s the Dockerfile for Metabase. It’s simple but gives us what we need:
# syntax=docker/dockerfile:1
# check=error=true
FROM metabase/metabase:v0.52.1
The bulk of the configuration happens in config/deploy.yml
. Below is the complete file with inline comments:
# Name of your application.
service: PROJECT
registry: # DockerHub credentials.
username: USERNAME
password:
- KAMAL_REGISTRY_PASSWORD
# DockerHub image details.
image: USERNAME/IMAGE_NAME
servers:
web:
- 1.2.3.4 # Public IP address of the server.
proxy:
ssl: true
host: WWW.DOMAIN.COM
app_port: 3000
healthcheck:
path: /api/health
timeout: 300 # Metabase can take a long time to boot
env:
clear:
MB_DB_TYPE: postgres
# Update PROJECT to match the name of the service above
# pg is from the accessory name.
# This uses docker networking to connect directly the the pg instance
MB_DB_HOST: PROJECT-pg
MB_DB_PORT: 5432
MB_DB_DBNAME: metabase
MB_DB_USER: metabase
MB_JETTY_PORT: 3000
JAVA_OPTS: -Xmx2g
secret:
- MB_DB_PASS
builder:
arch: amd64
aliases:
logs: app logs -f
accessories:
pg:
image: postgres:16.1-alpine
host: 1.2.3.4 # This is the public IP address for the machine instance
port: 5432:5432 # this only exposes the server to the docker network
directories:
- data:/var/lib/postgresql/data
env:
clear:
POSTGRES_USER: metabase
POSTGRES_DB: metabase
secret:
- POSTGRES_PASSWORD
For the PostgreSQL password, I created a secure file .kamal/env/postgres_password, which is ignored by Git to keep secrets safe. I exposed the secret to Kamal using .kamal/secrets
:
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD # From environment
POSTGRES_PASSWORD=$(cat .kamal/env/postgres_password)
MB_DB_PASS=$(cat .kamal/env/postgres_password)
Run kamal setup
once to initialize the server and kamal deploy
to push your configuration live. For convenience, I added a kamal logs alias to tail the application logs.
Here’s where Kamal shines. Updating Metabase for a patch is a breeze:
1. Update the Dockerfile:
# FROM metabase/metabase:v0.52.1 # Old version
FROM metabase/metabase:v0.52.1.1 # New version
2. Run kamal deploy.
That’s it—your updated Metabase instance is live!
This deployment method keeps things clean, manageable, and resilient to changes. Kamal’s versatility truly makes it a go-to tool for containerized apps.