How To Deploy Metabase with Docker Compose and DeployHQ

Devops & Infrastructure, Docker, Java, Open Source, Tips & Tricks, Tutorials, and VPS

How To Deploy Metabase with Docker Compose and DeployHQ

Self-hosted business intelligence gives your team full control over data, queries, and uptime. Metabase is one of the most popular open-source BI tools — and with Docker Compose, you can install Metabase and have a production-grade instance running in under 30 minutes.

This guide walks through deploying Metabase with Docker Compose on an Ubuntu 24.04 VPS, using PostgreSQL as the application database, Nginx as a reverse proxy, TLS via Let's Encrypt, and automated deployments through DeployHQ.

flowchart LR
    User["Browser"] -->|HTTPS :443| Nginx
    Nginx -->|proxy_pass :3000| Metabase
    Metabase -->|port 5432| PostgreSQL
    DeployHQ["DeployHQ"] -->|SSH commands| Server["Ubuntu 24.04"]
    Git["Git repo"] -->|push| DeployHQ

What you will need

  • An Ubuntu 24.04 LTS server with at least 2 CPUs and 4 GB RAM (a basic VPS works well)
  • A sudo-enabled non-root user and a firewall configured with ufw
  • Docker Engine installed — follow Docker's official Ubuntu instructions (includes the Compose plugin)
  • A DeployHQ account (free trial available)
  • A Git repository (GitHub, GitLab, or Bitbucket) for your configuration files
  • A domain name with an A record pointing to your server's public IP

Why Ubuntu 24.04? It is the current Long-Term Support release, with free security updates until June 2029. Ubuntu 20.04 reached end of standard support in May 2025 and should no longer be used for new deployments.


Step 1 — Verify Docker and the Compose plugin

Modern Docker Engine ships with Compose v2 as a built-in plugin. Confirm both are available:

docker --version
docker compose version

You should see output similar to:

Docker version 27.x.x, build xxxxxxx
Docker Compose version v2.x.x

Note: The older standalone docker-compose (with a hyphen) was deprecated in 2023 and removed from Docker Desktop. Always use docker compose (two words) going forward.

If you do not see the Compose plugin, install it:

sudo apt update
sudo apt install docker-compose-plugin

Step 2 — Create the project structure

On your local machine, create a directory for the Metabase configuration files that you will store in Git:

mkdir metabase-deploy && cd metabase-deploy
git init

Create a .gitignore to keep runtime data out of version control:

.env

Create a .env file with your database credentials. Never commit this file — you will copy it to the server manually once:

# .env — copy to ~/metabase-deploy/.env on the server
POSTGRES_USER=metabase
POSTGRES_PASSWORD=CHANGE_ME_TO_A_STRONG_RANDOM_VALUE
POSTGRES_DB=metabaseappdb

Step 3 — Write the Docker Compose file

Create docker-compose.yml. This configuration runs Metabase with a dedicated PostgreSQL database — the recommended setup for production:

services:
  metabase:
    image: metabase/metabase:v0.53.4
    container_name: metabase
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      MB_DB_TYPE: postgres
      MB_DB_DBNAME: ${POSTGRES_DB}
      MB_DB_PORT: 5432
      MB_DB_USER: ${POSTGRES_USER}
      MB_DB_PASS: ${POSTGRES_PASSWORD}
      MB_DB_HOST: postgres
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 120s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  postgres:
    image: postgres:16-alpine
    container_name: metabase-postgres
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  pgdata:

Key decisions in this file:

  • Pinned Metabase version (v0.53.4) instead of latest — avoids surprise breaking changes during deploys. Update this deliberately when you are ready to upgrade.
  • PostgreSQL 16 as the application database instead of the embedded H2 database, which Metabase warns is not suitable for production due to data-loss risks.
  • Health checks on both services ensure Compose waits for PostgreSQL to be ready before starting Metabase, and Docker can detect and restart unhealthy containers.
  • Bind to 127.0.0.1 on port 3000 so Metabase is only reachable through Nginx, not directly from the internet.
  • Log rotation prevents container logs from filling the disk.
  • No version key — the top-level version field is obsolete in Compose v2 and generates warnings.

Commit and push:

git add docker-compose.yml .gitignore
git commit -m "Add Metabase + PostgreSQL Docker Compose config"
git remote add origin YOUR_REPOSITORY_URL
git push -u origin main

Step 4 — Set up DeployHQ

DeployHQ automates the process of pushing configuration changes from Git to your server and restarting the containers. Here is how to wire it up:

  1. Create a new project in DeployHQ and connect your Git repository (deploy from GitHub or deploy from GitLab).
  2. Add your server as a deployment target using SFTP or SSH. Set the deployment path to ~/metabase-deploy.
  3. Configure SSH commands that run on every deployment:
Timing Command Purpose
Before upload cd ~/metabase-deploy && docker compose down Stop running containers gracefully
After upload cd ~/metabase-deploy && docker compose up -d --remove-orphans Start updated containers in the background

DeployHQ runs SSH commands from the user's $HOME directory by default, so the cd prefix is required. See the SSH commands documentation and deployment command examples for more patterns.

  1. Copy .env to the server manually (one-time step):
scp .env your_user@your_server_ip:~/metabase-deploy/.env
  1. Trigger a deployment from DeployHQ. The SSH commands will pull the images and start the stack.

Verify Metabase is running:

curl -s http://localhost:3000/api/health
# Expected: {"status":"ok"}

Step 5 — Install and configure Nginx

Nginx acts as a reverse proxy in front of Metabase, handling TLS termination and serving the application on ports 80/443.

Install Nginx and allow HTTP/HTTPS through the firewall:

sudo apt install nginx -y
sudo ufw allow "Nginx Full"

Create a server block at /etc/nginx/sites-available/metabase.conf:

server {
    listen 80;
    listen [::]:80;
    server_name your_domain_here;

    access_log /var/log/nginx/metabase.access.log;
    error_log  /var/log/nginx/metabase.error.log;

    location / {
        proxy_pass         http://127.0.0.1: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;

        # WebSocket support for Metabase real-time features
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
    }
}

Enable the site, test, and reload:

sudo ln -s /etc/nginx/sites-available/metabase.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Visit http://your_domain_here — you should see the Metabase setup wizard.


Step 6 — Secure the connection with Let's Encrypt

Install Certbot via snap (the recommended method — the apt package is outdated on many distributions):

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

Obtain a certificate and automatically configure Nginx for HTTPS:

sudo certbot --nginx -d your_domain_here

Certbot will:

  • Obtain a Let's Encrypt certificate
  • Update your Nginx config to redirect HTTP to HTTPS
  • Set up automatic renewal via a systemd timer

Verify the renewal timer is active:

sudo certbot renew --dry-run

Step 7 — Complete the Metabase setup wizard

Open https://your_domain_here in your browser. The first-run wizard will ask you to:

  1. Choose your language
  2. Create an admin account
  3. Optionally connect a data source (you can add databases later)
  4. Choose your analytics tracking preferences

Once complete, you have a fully functional, production-ready Metabase instance.


Step 8 — Deploy updates with DeployHQ

The workflow for ongoing changes is straightforward:

  1. Edit docker-compose.yml locally (for example, to upgrade the Metabase version)
  2. Commit and push to Git
  3. DeployHQ detects the new commit and can auto-deploy or wait for your approval
  4. The SSH commands stop the old containers, upload the new config, and restart

To upgrade Metabase, change the image tag in docker-compose.yml:

    image: metabase/metabase:v0.55.0   # update to desired version

Commit, push, and deploy. DeployHQ handles the rest.

Rolling back

If a deployment introduces issues, open the DeployHQ dashboard, navigate to your project's deployment history, and click Redeploy this version on the last known-good deployment.


Backing up Metabase data

Since Metabase stores dashboards, questions, and user accounts in PostgreSQL, back up the database regularly:

docker exec metabase-postgres pg_dump -U metabase metabaseappdb > metabase-backup-$(date +%F).sql

To restore:

cat metabase-backup-2026-03-25.sql | docker exec -i metabase-postgres psql -U metabase metabaseappdb

Consider adding a cron job or a DeployHQ shell server to automate daily backups.


Troubleshooting

Metabase takes a long time to start on the first run This is normal — it needs to run database migrations. The start_period: 120s in the health check gives it time. Check logs with docker compose logs -f metabase.

docker compose command not found Your Docker installation may be missing the Compose plugin. Install it with sudo apt install docker-compose-plugin. Do not install the legacy docker-compose package.

502 Bad Gateway from Nginx Metabase is not ready yet or has crashed. Check docker compose ps to see container status and docker compose logs metabase for errors.

Certificate renewal fails Run sudo certbot renew --dry-run to diagnose. Ensure port 80 is open and Nginx is running. Check /var/log/letsencrypt/letsencrypt.log for details.

Deployment fails in DeployHQ Check the DeployHQ deployment logs for the exact error. Common causes: SSH key mismatch, wrong deployment path, or the server running out of disk space.


What to explore next

  • Connect data sources: Add your MySQL, PostgreSQL, or other databases in Metabase's Admin > Databases panel
  • Set up email: Configure SMTP in Admin > Settings > Email so Metabase can send alerts and scheduled reports
  • Enable embedding: Embed dashboards in your own applications using signed URLs or full embedding
  • Scale with DeployHQ: If you are managing multiple services, explore build pipelines and shell servers for more complex workflows

Ready to automate your deployments? Sign up for DeployHQ and connect your first project in minutes.

For questions or help, reach out to support@deployhq.com or find us on X (@deployhq).