Jonathan Bennett

Kamal Isn’t Just for Rails…

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.


1. Spin Up a Machine

For this deployment, I used a 2GB RAM Linode instance to handle the workload. After creating the machine:

  • Noted the public IP address.
  • Added my SSH key for access.
  • Configured a firewall to allow HTTP, HTTPS, and SSH traffic only.

2. Set Up a Local Project

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.


3. Create the Dockerfile

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

4. Configure Deployment

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.


5. Updating Metabase

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.