Self-hosted business intelligence gives your team full control over data, queries, and uptime. [Metabase](https://www.metabase.com/) 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](https://www.deployhq.com).

```
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](https://www.deployhq.com/blog/category/vps) works well)
- A sudo-enabled non-root user and a firewall configured with `ufw`
- Docker Engine installed — follow [Docker's official Ubuntu instructions](https://docs.docker.com/engine/install/ubuntu/) (includes the Compose plugin)
- A [DeployHQ account](https://www.deployhq.com/signup) (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](https://www.metabase.com/learn/metabase-basics/administration/administration-and-operation/metabase-in-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](https://www.metabase.com/docs/latest/installation-and-operation/running-metabase-on-docker) 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](https://docs.docker.com/compose/intro/history/) 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](https://www.deployhq.com) 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](https://www.deployhq.com) and connect your Git repository ([deploy from GitHub](https://www.deployhq.com/deploy-from-github) or [deploy from GitLab](https://www.deployhq.com/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](https://www.deployhq.com/support/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](https://www.deployhq.com) runs SSH commands from the user's `$HOME` directory by default, so the `cd` prefix is required. See the [SSH commands documentation](https://www.deployhq.com/support/ssh-commands) and [deployment command examples](https://www.deployhq.com/support/deployments/deployment-commands-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](https://www.deployhq.com). 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](https://www.deployhq.com/blog/what-is-docker) 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](https://certbot.eff.org/instructions?ws=nginx&os=snap) — 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](https://www.deployhq.com) detects the new commit and can [auto-deploy](https://www.deployhq.com/support/deployments/deployment-commands-examples) 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](https://www.deployhq.com) handles the rest.

### Rolling back

If a deployment introduces issues, open the [DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com/support/servers/adding-a-server/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](https://www.deployhq.com/support) 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](https://www.metabase.com/docs/latest/embedding/introduction) in your own applications using signed URLs or full embedding
- **Scale with DeployHQ** : If you are managing multiple services, explore [build pipelines](https://www.deployhq.com/blog/build-pipelines-in-deployhq-streamline-your-deployment-workflow) and [shell servers](https://www.deployhq.com/support/servers/adding-a-server/shell-server) for more complex workflows

* * *

Ready to automate your deployments? [Sign up for DeployHQ](https://www.deployhq.com/signup) and connect your first project in minutes.

For questions or help, reach out to [support@deployhq.com](mailto:support@deployhq.com) or find us on [X (@deployhq)](https://x.com/deployhq).

