Running Metabase with Docker - A Step-by-Step Guide
Docker is the standard way to run Metabase in both development and production. Metabase publishes an official Docker image that packages the entire ap...
Running Metabase with Docker: A Step-by-Step Guide
Docker is the standard way to run Metabase in both development and production. Metabase publishes an official Docker image that packages the entire application — JVM, application server, and dependencies — into a single portable container. This guide covers everything from a one-liner local setup to a production-grade Docker Compose configuration with persistent storage, environment-based secrets, and health checks.
---
Why Docker for Metabase
Metabase is a Java application. Running it without Docker requires installing a compatible JVM, managing the JAR file, configuring system services, and handling OS-level dependencies. Docker eliminates all of that — the image contains everything Metabase needs to run, and the same image works identically on a developer's laptop, a CI server, and a production VM.
Additional advantages of the Docker deployment model:
- Reproducible environments — the same image version produces the same behavior everywhere
---
The Official Docker Image
Metabase publishes its Docker image to Docker Hub:
metabase/metabase — the open-source editionmetabase/metabase-enterprise — the Pro and Enterprise editionBoth images use the same base configuration. Feature availability is determined by the license key you apply, not the image itself.
Image Tags
| Tag | Description |
|---|---|
latest | Latest stable release |
v0.50.x | Specific version (recommended for production) |
v1.50.x | Enterprise edition of the same version |
latest means automatic upgrades on container restart, which can be unexpected. ---
Basic Local Setup
The minimum command to run Metabase locally:
bash
docker run -d \ -p 3000:3000 \ --name metabase \ metabase/metabase
-d runs the container in detached mode (background)-p 3000:3000 maps port 3000 on your machine to port 3000 in the container--name metabase gives the container a name for easier managementMetabase will be available at http://localhost:3000 after 60–90 seconds (first startup is slower due to JVM initialization and schema migrations).
To follow startup logs:
bash
docker logs -f metabase
Look for Metabase Initialization COMPLETE to confirm startup succeeded.
---
Persisting Data
By default, the basic docker run command stores Metabase's application data (saved questions, dashboards, users, settings) in an embedded H2 database inside the container. This data is lost when the container is removed.
Option 1: Mount a Volume for H2 (Development Only)
bash
docker run -d \ -p 3000:3000 \ --name metabase \ -v ~/metabase-data:/metabase-data \ -e MB_DB_FILE=/metabase-data/metabase.db \ metabase/metabase
This persists the H2 database to your host filesystem. Acceptable for local development. Not recommended for production.
Option 2: Use PostgreSQL as the Application Database (Production)
bash
docker run -d \ -p 3000:3000 \ --name metabase \ -e MB_DB_TYPE=postgres \ -e MB_DB_DBNAME=metabase \ -e MB_DB_PORT=5432 \ -e MB_DB_USER=metabase_app \ -e MB_DB_PASS=your-password \ -e MB_DB_HOST=your-postgres-host \ metabase/metabase
PostgreSQL is the recommended application database for all production deployments. It supports concurrent connections, provides reliable backups, and is well-supported by every managed database service.
---
Production Docker Compose Configuration
For most teams, Docker Compose provides the right balance of simplicity and control. Here's a production-ready configuration:
yaml
version: '3.9'
services: metabase: image: metabase/metabase:v0.50.0 container_name: metabase restart: unless-stopped ports: - "3000:3000" environment: # Application database MB_DB_TYPE: postgres MB_DB_DBNAME: ${METABASE_DB_NAME:-metabase} MB_DB_PORT: ${METABASE_DB_PORT:-5432} MB_DB_USER: ${METABASE_DB_USER} MB_DB_PASS: ${METABASE_DB_PASS} MB_DB_HOST: db
# Site configuration MB_SITE_URL: ${METABASE_SITE_URL} MB_SITE_NAME: ${METABASE_SITE_NAME:-Metabase}
# Embedding (Pro/Enterprise) MB_EMBEDDING_SECRET_KEY: ${METABASE_EMBEDDING_SECRET_KEY}
# Performance JAVA_OPTS: "-Xmx2g -Xms512m"
# Disable anonymous telemetry (optional) MB_ANON_TRACKING_ENABLED: "false" depends_on: db: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 5 start_period: 90s
db: image: postgres:15-alpine container_name: metabase-db restart: unless-stopped environment: POSTGRES_DB: ${METABASE_DB_NAME:-metabase} POSTGRES_USER: ${METABASE_DB_USER} POSTGRES_PASSWORD: ${METABASE_DB_PASS} volumes: - metabase-db-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${METABASE_DB_USER}"] interval: 10s timeout: 5s retries: 5
volumes: metabase-db-data: driver: local
And a corresponding .env file:
bash
METABASE_DB_NAME=metabase METABASE_DB_PORT=5432 METABASE_DB_USER=metabase_app METABASE_DB_PASS=a-strong-password METABASE_SITE_URL=https://analytics.yourapp.com METABASE_SITE_NAME=Analytics METABASE_EMBEDDING_SECRET_KEY=a-long-random-secret-for-embedding
Start with:
bash
docker compose up -d
Key Configuration Decisions in This Setup
restart: unless-stopped — The container restarts automatically if it crashes or if the host reboots, unless you manually stop it. Essential for production.
depends_on with condition: service_healthy — Metabase won't start until PostgreSQL passes its health check. Without this, Metabase can start before the database is ready and fail on first connection.
JAVA_OPTS: "-Xmx2g -Xms512m" — Sets the JVM heap size. -Xmx2g caps heap at 2GB. Increase for larger deployments or if you see out-of-memory errors. The JVM will consume additional memory beyond the heap, so plan for 3–4GB total container memory.
MB_EMBEDDING_SECRET_KEY — Required if you're using signed embedding. This value must be consistent across container restarts — if it changes, all existing signed embed URLs stop working.
Health check — Polls Metabase's /api/health endpoint. Docker will mark the container unhealthy if it stops responding, which triggers alerts in monitoring systems.
---
Networking Considerations
Container-to-Container Communication
When running Metabase and its application database in the same Docker Compose stack, they communicate over Docker's internal network. The MB_DB_HOST value should be the service name (db), not localhost.
Connecting to an External Database
If your application database or data source is outside Docker (e.g., a managed RDS instance):
bash
-e MB_DB_HOST=your-rds-endpoint.us-east-1.rds.amazonaws.com
Ensure your security groups or firewall rules allow the container's outbound IP to reach the database port.
Reverse Proxy
In production, Metabase should sit behind a reverse proxy (nginx, Caddy, or a cloud load balancer) that handles:
Example nginx configuration:
nginx
server { listen 443 ssl; server_name analytics.yourapp.com;
ssl_certificate /etc/ssl/certs/yourapp.crt; ssl_certificate_key /etc/ssl/private/yourapp.key;
location / { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
Set MB_SITE_URL to the public HTTPS URL, not the internal Docker port.
---
Resource Requirements
Metabase's resource requirements depend on the number of concurrent users and query complexity:
| Deployment Size | RAM | CPU | Notes |
|---|---|---|---|
| Development / evaluation | 1GB | 1 vCPU | H2 database, few users |
| Small team (< 20 users) | 2GB | 2 vCPU | PostgreSQL app DB recommended |
| Medium team (20–100 users) | 4GB | 2–4 vCPU | Caching recommended |
| Large deployment (100+ users) | 8GB+ | 4+ vCPU | Consider horizontal scaling or Metabase Cloud |
-Xmx in JAVA_OPTS. ---
Managing the Container
Useful Docker Commands
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">View logs</h1> docker logs -f metabase
<h1 class="text-4xl font-bold mb-6 text-slate-900">Restart the container</h1> docker restart metabase
<h1 class="text-4xl font-bold mb-6 text-slate-900">Check container status</h1> docker ps -a --filter name=metabase
<h1 class="text-4xl font-bold mb-6 text-slate-900">Shell into the container for debugging</h1> docker exec -it metabase bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">Check health status</h1> docker inspect --format='{{.State.Health.Status}}' metabase
Upgrading
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">Pull the new version</h1> docker pull metabase/metabase:v0.51.0
<h1 class="text-4xl font-bold mb-6 text-slate-900">Update docker-compose.yml to the new version tag, then:</h1> docker compose pull docker compose up -d
Metabase applies database migrations on startup. Back up your application database before upgrading.
Backup
Back up the PostgreSQL application database on a regular schedule:
bash
docker exec metabase-db pg_dump -U metabase_app metabase > metabase-backup-$(date +%Y%m%d).sql
The application database contains all of Metabase's configuration — users, dashboards, saved questions, permissions, and settings. Your actual analytics data lives in your connected databases and is not stored in Metabase.
---
Troubleshooting
Container exits immediately on startup
Check the logs: docker logs metabase. Common causes:
"Database is locked" error
Caused by concurrent H2 access or an unclean shutdown. If you're using H2 (not recommended for production), delete the .db.lock file and restart.
Queries fail with connection errors
Verify the data source connection from within the container:
bash
docker exec -it metabase bash <h1 class="text-4xl font-bold mb-6 text-slate-900">Then test connectivity to your database host</h1>
Metabase is slow after startup
JVM needs a few minutes to warm up after the first startup. Query performance on subsequent runs is significantly faster.
---
Summary
Docker is the fastest and most reliable way to run Metabase. For development, a single docker run command gets you started in under two minutes. For production, Docker Compose with a PostgreSQL application database, a health check, and proper environment variable management gives you a maintainable, upgradeable deployment. The most important production decisions are: use PostgreSQL (not H2), pin to a specific image version, and set MB_SITE_URL correctly.