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 usedocker 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 oflatest— 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
versionkey — the top-levelversionfield 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:
- Create a new project in DeployHQ and connect your Git repository (deploy from GitHub or deploy from GitLab).
- Add your server as a deployment target using SFTP or SSH. Set the deployment path to
~/metabase-deploy. - 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
$HOMEdirectory by default, so thecdprefix is required. See the SSH commands documentation and deployment command examples for more patterns.
- Copy
.envto the server manually (one-time step):
scp .env your_user@your_server_ip:~/metabase-deploy/.env
- 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:
- Choose your language
- Create an admin account
- Optionally connect a data source (you can add databases later)
- 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:
- Edit
docker-compose.ymllocally (for example, to upgrade the Metabase version) - Commit and push to Git
- DeployHQ detects the new commit and can auto-deploy or wait for your approval
- 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).