Production ready docker compose file to deploy self-hosted Metabase with SSL

Hello Metabase Community,

Here is a docker compose that I put together for production use (three months active use at this point):

Postgres (with encrypted connection information) is used for application data. Traefik sits in front of Metabase to provide SSL termination (and generates Let's Encrypt certificates).

Hopefully others in the community who are self-hosting might find this useful.

I'm also very keen to receive feedback or suggestions to improve/harden the configuration (either here or in the repository if there is an issue).


Thanks James! good work!

1 Like

Thank you to everyone at Metabase! I've posted a write up with our use case here:

Thanks for this, James, as an apprentice starting out this has been an incredibly useful resource. And the write up was great for use case context as well.

I have been looking into modifying the template to use mysql rather than postgres as the app DB. I believe I've actioned every postgres mention to mysql, changing the docker image version to mysql:5.7 etc.

However on 'docker compose up -d' I am getting a forever spinning circle.

Verifying mysql Database Connection ... 2023-11-14 19:11:56,737 ERROR middleware.log :: GET /api/health 503 319.1 µs (0 DB calls) {:status "initializing", :progress 0.3}

Everything online seems to suggest a database lock, but can that be the case on a fresh install / running of the file?

I imagine the configuration simply isn't as simple as switching postgres for mysql, am I on the right track with that thought? I am getting a bit turned around as I look into this, so I'd welcome any ideas. Thanks so much for your time.

Hi @bkb,

I think my first question would be (and I hope you don't mind me asking!): why do you want to use MySQL in place of Postgres?

My first thought (and hopefully you are already aware of this so apologies if I've gone too basic) is that environmental variables are not standardised across docker containers. Simply swapping postgres for mysql wont work. You'll need to look at the variables exposed by each container and then port the postgres config to the variables the mysql container is expecting.

If you are comfortable posting your docker compose file that you've created as it will be faster to get to the bottom of why Metabase can't initialise.

I do suggest sticking with postgres for production deployments and not deviating from the template too much if you want to have a (mostly) stress free life!

Thanks James, I appreciate the quick response to help! :slight_smile:

Essentially, at the company I'm in people are far more familiar with mySQL, so if I can deploy the application database with mysql I probably should, for the sake of those who will be using metabase. That is, in case they want/need to query questions etc through a CLI at any point.

I agree postgres would be the far easier deploy, so I'll see if I can get away with using that if using mysql proves too complicated!

I agree with what you've outlined, I'll have a double check of everything today to ensure I've used the correct variable names etc for mysql. If I still have issues, I'll share the compose file here - or convince everyone postgres for the app db is best!
Thanks again for your time.

I have worked more on this and still am probably overlooking something embarrassingly obvious. I note your point on the environment variables exposed by metabase, I thought I had them amended under mysql, those MB_DB_DBNAME etc under metabase should stay the same I think as they're what's expected by metabase?

version: '3.8'

    image: metabase/metabase:v0.47.7
    container_name: metabase
    hostname: metabase
      - /dev/urandom:/dev/random:ro
      - 3000:3000
      MB_DB_TYPE: mysql
      MB_DB_PORT: 3306
      MB_DB_USER_FILE: /run/secrets/db_user
      MB_DB_PASS_FILE: /run/secrets/db_password
      MB_DB_HOST: mysql
      MB_ENCRYPTION_SECRET_KEY: /run/secrets/mb_key
        - "traefik.enable=true"
        - "traefik.http.routers.meta.rule=Host(`${MB_HOST}`)"
        - "traefik.http.routers.meta.entrypoints=websecure"
        - "traefik.http.routers.meta.tls.certresolver=myresolver"
      - metanet1
      - mysql
      - db_password
      - db_user
      - mb_key
    restart: always

    image: mysql:5.7
    container_name: mysql
    hostname: mysql
      MYSQL_USER_FILE: /run/secrets/db_user
      MYSQL_PASSWORD_FILE: /run/secrets/db_password
      MYSQL_ROOT_PASSWORD: /run/secrets/db_password #just as example
      - ./mysql/data:/var/lib/mysql/data
      - metanet1
      - db_password
      - db_user
    restart: always

    image: traefik:v2.10.5
    container_name: traefik
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
      - "${LE_EMAIL}"
      - ""
      - "traefik.enable=true"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      - "traefik.http.routers.redirect-https.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.redirect-https.entrypoints=web"
      - "traefik.http.routers.redirect-https.middlewares=redirect-to-https"
      - "80:80"
      - "443:443"
      - metanet1
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    restart: always

    driver: bridge

     file: db_password.txt
     file: db_user.txt
     file: mb_key.txt