Why Metabase
Metabase is a very nice BI tool, makes it easy for non technical people to ask questions about data and visualize answers. It is open source, you can subscribe to a cloud version of it, but if want to self host it, then the guide in this article is in my opinion the fastest and cheapest way (on AWS).
I chose to do that in one of my projects because I found AWS QuickSight unnecessarily complicated and bloated, and I was suprised by how much time I wasted setting up security rules in AWS to make QuickSight work with RDS! I think I will write another article about that later.
Which AWS service to use?
Of course we can choose from many options, ECS, EKS, Elastic Beanstalk, EC2 only or any other option in AWS. I find it very easy to just use EC2 and install Metabase with a reverse proxy on it, and this works nicely for a limited amount of traffic. so if you are using metabase for internal analytics, expect predictable traffic on it, and want cheap easy and fast approach, then just create an EC2 and follow this guide.
I chose to use t3.small for this. you can of course run it on a smaller machine, depends on how big the queries and data that you expect your users to deal with, you might struggle with RAM, so you need to do some calculations here. of course you can run it even on t3.nano if you really want the cheapest possible setup.
Step by Step guide
Here is a step by step guide to do the following:
- Run Metabase on your EC2
- Run reverse proxy using Caddy
- Generate SSL certificate
- Create a custom domain name for your deployment in Route53
Security Groups
EC2 Security Group:
- Inbound rules:
- Port 22 (SSH) - from your IP (please DONT ENABLE PUBLIC ACCESS HERE)
- Port 80 (HTTP) - from anywhere (0.0.0.0/0)
- Port 443 (HTTPS) - from anywhere (0.0.0.0/0)
RDS Security Group: Only if you are using RDS already and want to use it for the database of metabase (the database where metabase stores the users and user content. this article will offer an alternative to using RDS)
- Inbound rule:
- Port 5432 (PostgreSQL) - from EC2’s Security Group (not from IP addresses)
Route 53
Create an A record pointing to your EC2 instance:
- Record name:
bi.mydomain.com(or your preferred subdomain) - Record type: A
- Value: EC2 public IP address
Tip: Use an Elastic IP for your EC2 instance to ensure the IP doesn’t change when you stop/start the instance.
Install Docker + Compose
SSH into your EC2 instance and run the following commands:
sudo dnf -y update
sudo dnf -y install docker
sudo systemctl enable --now docker
sudo usermod -aG docker ec2-user
newgrp docker
Install Docker Compose plugin:
mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \
-o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
docker compose version
Verify the installation by checking the version.
Create Metabase Metadata DB on RDS (Only if using RDS option)
Note: This step is only required if you’re using the RDS option. If you’re using the PostgreSQL container option, you can skip this step as the database will be created automatically.
Connect to your RDS PostgreSQL instance and create the Metabase database:
psql "host=<RDS-ENDPOINT> port=5432 user=<admin> dbname=postgres sslmode=require"
Once connected, run:
CREATE DATABASE metabase;
CREATE USER metabase_app WITH PASSWORD '<STRONG_PASSWORD>';
GRANT ALL PRIVILEGES ON DATABASE metabase TO metabase_app;
Replace:
<RDS-ENDPOINT>with your RDS endpoint<admin>with your RDS admin username<STRONG_PASSWORD>with a strong password for the Metabase application user
4️⃣ Create Files on EC2
Create a directory for your Metabase setup:
mkdir -p ~/metabase
cd ~/metabase
docker-compose.yml
Choose your database setup:
Option 1: Using RDS (Default)
Use this option if you already have an RDS PostgreSQL instance or want to use AWS RDS for your Metabase metadata database.
Option 2: With PostgreSQL Container
Use this option if you want to self-host PostgreSQL in a Docker container alongside Metabase. This is simpler to set up but requires managing the database yourself.
services:
metabase:
image: metabase/metabase:latest
container_name: metabase
environment:
MB_DB_TYPE: postgres
MB_DB_CONNECTION_URI: "jdbc:postgresql://<RDS-ENDPOINT>:5432/metabase?user=metabase_app&password=<STRONG_PASSWORD>&ssl=true&sslmode=require"
MB_SITE_URL: "https://bi.yourdomain.com"
JAVA_TOOL_OPTIONS: "-Xms512m -Xmx1024m"
restart: unless-stopped
caddy:
image: caddy:latest
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
depends_on:
- metabase
restart: unless-stopped
volumes:
caddy-data:
caddy-config:
services:
postgres:
image: postgres:15
container_name: metabase-postgres
environment:
POSTGRES_DB: metabase
POSTGRES_USER: metabase_app
POSTGRES_PASSWORD: <STRONG_PASSWORD>
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U metabase_app -d metabase"]
interval: 10s
timeout: 5s
retries: 5
metabase:
image: metabase/metabase:latest
container_name: metabase
environment:
MB_DB_TYPE: postgres
MB_DB_DBNAME: metabase
MB_DB_PORT: 5432
MB_DB_USER: metabase_app
MB_DB_PASS: <STRONG_PASSWORD>
MB_DB_HOST: postgres
MB_SITE_URL: "https://bi.yourdomain.com"
JAVA_TOOL_OPTIONS: "-Xms512m -Xmx1024m"
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
caddy:
image: caddy:latest
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
depends_on:
- metabase
restart: unless-stopped
volumes:
postgres-data:
caddy-data:
caddy-config:
Replace:
<RDS-ENDPOINT>with your RDS endpoint<STRONG_PASSWORD>with the password you created formetabase_appbi.yourdomain.comwith your domain name
Replace:
<STRONG_PASSWORD>with a strong password for the PostgreSQL databasebi.yourdomain.comwith your domain name
<p><strong>Note:</strong> With this option, you don't need to create the database manually. The PostgreSQL container will automatically create the database when it starts for the first time.</p>
Caddyfile
Create Caddyfile:
bi.yourdomain.com {
encode gzip
reverse_proxy metabase:3000
header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
tls {
protocols tls1.2 tls1.3
}
}
Replace bi.yourdomain.com with your domain name.
Launch
Start the containers:
cd ~/metabase
docker compose up -d
Caddy will automatically obtain a Let’s Encrypt TLS certificate for your domain. This requires:
- Your domain to be properly configured in Route 53
- Ports 80 and 443 to be accessible from the internet
- The domain to resolve to your EC2 instance’s IP
Verify
Check that containers are running:
docker ps
Monitor the Metabase logs to ensure initialization completes:
docker logs -f metabase
Wait for the message: “Metabase Initialization COMPLETE”
Check Caddy logs to verify the certificate was obtained:
docker logs -f caddy
You should see messages indicating successful certificate acquisition.
Access Your Metabase Instance
Visit https://bi.yourdomain.com (or your configured domain) in your browser. You should see the Metabase setup screen where you can create your admin account and configure your first database connection.
Need help with your AWS deployments? Contact me here.