Getting Started

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...

📅
📖8 min read

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
  • Easy upgrades — swap image versions; Metabase handles migrations on startup
  • Environment variable configuration — all configuration is passed as env vars, making it compatible with secrets managers and container orchestration platforms
  • Easy rollbacks — pin to a specific image version tag and roll back by restarting with the previous tag
  • ---

    The Official Docker Image

    Metabase publishes its Docker image to Docker Hub:

  • metabase/metabase — the open-source edition
  • metabase/metabase-enterprise — the Pro and Enterprise edition
  • Both images use the same base configuration. Feature availability is determined by the license key you apply, not the image itself.

    Image Tags

    TagDescription
    latestLatest stable release
    v0.50.xSpecific version (recommended for production)
    v1.50.xEnterprise edition of the same version
    For production, always pin to a specific version tag. Using 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 management
  • Metabase 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:

  • TLS termination
  • Domain routing
  • Optional rate limiting
  • 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 SizeRAMCPUNotes
    Development / evaluation1GB1 vCPUH2 database, few users
    Small team (< 20 users)2GB2 vCPUPostgreSQL app DB recommended
    Medium team (20–100 users)4GB2–4 vCPUCaching recommended
    Large deployment (100+ users)8GB+4+ vCPUConsider horizontal scaling or Metabase Cloud
    The JVM is the primary memory consumer. Most out-of-memory issues are solved by increasing -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:

  • Can't reach the application database (wrong host, port, or credentials)
  • Port 3000 is already in use on the host
  • Insufficient memory
  • "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.