Bad Gateway for WordPress containers behind Traefik reverse proxy in docker-compose

Firstly, I'd like to say that I'm not a server admin. I'm a web programmer tasked with setting up a development server and I have no idea what I'm doing. I may not be doing things according to best practice or the way you might do them. Unfortunately, there are 3 ways to do everything and so 2/3 of the answers that I've come across aren't compatible with my implementation and I can't figure out how to make them work. Furthermore, this isn't my only (or even primary) job duty.

Here's the setup:

Single-server docker environment on a Linode server with Ubuntu 20.04

I have one stack with Traefik, Traefik Hub, Portainer, and WhoAmI configured and working (mostly) correctly. I don't have the DNS challenge working right with Let's Encrypt, but I don't really care about that at this point. I don't really need a wildcard certificate.

I created a mariadb container. We're mostly a WordPress shop and I'd like to have one container for all the databases we work with rather than configuring a database on an environment-by-environment basis.

I created an external bridge network, named "maverick-net" and all of the stacks are connected to it.

I have a self-hosted GitHub runner listening for changes to the "dev" branch of the project. The runner pulls down the latest repo, writes GitHub secrets to a local .env file, runs composer install and then docker-compose up -d. (That's the reason behind the obscenely-long bind mount paths.)

I'm trying to make the code for these WordPress projects reusable as much as possible, so there's a lot of .env variables in the different files. At some point I'll probably move those over to docker secrets, but at this point it's a development server and not as critical.

My issue is that I haven't been able to get a WordPress site up and running, and I keep hitting a "Bad Gateway" error. When I curl the URL from inside the traefik container, I get... wait for it... "Bad Gateway."

Clearly there's something I'm missing, but I've been slamming my head against a brick wall for weeks trying different approaches to get this running and I need help. There has to be something I'm not getting about docker networks in general because my wp-cli container never has been able to connect to the database, regardless of whether I start it in the same stack or if i try to connect to it on the maverick-net network.

My traefik stack (side note, I'd really like to split these command entries into static and dynamic config files, which i've tried with limited success, but that's a task for another day):

version: "3.9"

secrets:
  linode_token:
    file: "../secrets/linode_token.secret"

services:

  traefik:
    container_name: traefik
    image: "traefik:latest"
    command:
      - --log.level=DEBUG
      - --log.filePath=./traefik.log
      - --accessLog=true
      - --accessLog.filePath=./access.log
      - --accessLog.bufferingSize=100
      - --accessLog.filters.statusCodes=400-499
      - --api
      - --api.dashboard=true
      - --api.insecure=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --providers.docker
      - --providers.docker.watch=true
      - --providers.docker.exposedbydefault=false
      - --certificatesresolvers.leresolver.acme.dnsChallenge=true
      - --certificatesresolvers.leresolver.acme.dnsChallenge.provider=linodev4
      - --certificatesresolvers.leresolver.acme.httpchallenge=true
      - --certificatesresolvers.leresolver.acme.httpchallenge.entrypoint=web
      - --certificatesresolvers.leresolver.acme.email=xxxxxxxxxxx@xxxxxxxxx.xxx
      - --certificatesresolvers.leresolver.acme.storage=./acme.json
      #- --certificatesresolvers.leresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --certificatesresolvers.leresolver.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
      - --experimental.hub=true
      - --hub.tls.insecure=true
      - --metrics.prometheus.addrouterslabels=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ~/certs-data/acme.json:/data/letsencrypt/acme.json
      - ./static.yml:/static.yml:ro
      - ./configs:/configs
      - ~/certs-data/:/data/letsencrypt/
    secrets:
      - "linode_token"
    environment:
      TZ: America/Chicago
      LINODE_TOKEN_FILE: "/run/secrets/linode_token"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=maverick-net"
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      - "traefik.http.routers.traefik.tls.certresolver=leresolver"
      - "traefik.http.routers.traefik.rule=Host(`XXXXX.XXXXXXXXXX.XXX`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.middlewares=traefik-auth"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=XXXX:$$apr1$$XXXXX$$XXXXXXXXXXXXXXX"
      - "traefik.http.routers.api.entrypoints=websecure"
    networks:
      - maverick-net

  hub-agent:
    image: ghcr.io/traefik/hub-agent-traefik:experimental
    pull_policy: always
    container_name: hub-agent
    restart: on-failure
    command:
      - run
      - --hub.token=XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX
      - --auth-server.advertise-url=http://hub-agent
      - --traefik.host=traefik
      - --traefik.tls.insecure=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - traefik
    networks:
      - maverick-net

  portainer:
    image: portainer/portainer-ce:latest
    command: -H unix:///var/run/docker.sock
    container_name: portainer
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    labels:
      # Frontend
      - "traefik.enable=true"
      - "traefik.http.routers.frontend.rule=Host(`XXXXX.XXXXXXXXXX.XXX`)"
      - "traefik.http.routers.frontend.entrypoints=websecure"
      - "traefik.http.services.frontend.loadbalancer.server.port=9000"
      - "traefik.http.routers.frontend.service=frontend"
      - "traefik.http.routers.frontend.tls.certresolver=leresolver"
    networks:
      - maverick-net

  whoami:
    image: "traefik/whoami"
    container_name: "whoami"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`XXXXX.XXXXXXXXXX.XXX`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=leresolver"
    networks:
      - maverick-net

volumes:
  portainer_data:
networks:
  maverick-net:
    external: true

my mariadb stack:

version: "3"

networks:
  # enable connection with Traefik
  maverick-net:
    external: true

services:
  mariadb:
    container_name: mariadb
    image: mariadb:10.7
    restart: always
    volumes:
      - "/home/xxxxxxxxxx/docker/mariadb/data:/var/lib/mysql"
    expose:
      - "3306"
    env_file: .env
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PWD}
      MYSQL_USER: ${ADMIN_DB_USER}
      MYSQL_PASSWORD: ${ADMIN_DB_PWD}
    networks:
      - maverick-net

and finally my wordpress stack:

version: '3.8'

networks:
  maverick-net:
    external: true
# volumes:
#   db_data:

services:

  # mariadb:
  #   container_name: ${WORDPRESS_DB_NAME}-db
  #   image: mariadb:10.7
  #   restart: always
  #   volumes:
  #     - "db_data:/var/lib/mysql"
  #   env_file: .env
  #   environment:
  #     MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
  #     MYSQL_USER: ${ADMIN_DB_USER}
  #     MYSQL_PASSWORD: ${ADMIN_DB_PWD}

  wordpress:
    container_name: ${WORDPRESS_DB_NAME}-wp
    image: wordpress:6.0.2-fpm
    volumes:
      - type: bind
        source: ${PROJECT_ROOT}/${WORDPRESS_DB_NAME}/${PROJECT_NAME}/${PROJECT_NAME}/wp
        target: /var/www/html
    restart: always
    env_file: .env
    environment:
      WORDPRESS_DB_HOST: mariadb
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
      WORDPRESS_DATABASE_USER: ${WORDPRESS_DB_USER}
      WORDPRESS_DATABASE_PASSWORD: ${WORDPRESS_DB_PASSWORD}
      WORDPRESS_DATABASE_NAME: ${WORDPRESS_DB_NAME}
    labels:
      # The labels are useful for Traefik only
      - "traefik.enable=true"
      - "traefik.docker.network=maverick-net"
      # Get the routes from https
      - "traefik.http.routers.${WORDPRESS_DB_NAME}.rule=Host(`${DEV_URL}`)"
      - "traefik.http.routers.${WORDPRESS_DB_NAME}.entrypoints=websecure"
      - "traefik.http.routers.${WORDPRESS_DB_NAME}.tls.certresolver=leresolver"
    networks:
      - maverick-net

  wordpress-cli:
    container_name: ${WORDPRESS_DB_NAME}-cli
    image: wordpress:cli
    volumes:
      - type: bind
        source: ${PROJECT_ROOT}/${WORDPRESS_DB_NAME}/${PROJECT_NAME}/${PROJECT_NAME}/wp
        target: /var/www/html
    env_file: .env
    environment:
      WORDPRESS_DB_HOST: mariadb
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
      WORDPRESS_DATABASE_USER: ${WORDPRESS_DB_USER}
      WORDPRESS_DATABASE_PASSWORD: ${WORDPRESS_DB_PASSWORD}
      WORDPRESS_DATABASE_NAME: ${WORDPRESS_DB_NAME}
    networks:
      - maverick-net
    depends_on:
      - wordpress

Hi,

Wordpress Docker image you use (wordpress:6.0.2-fpm) don't have any webserver. You have to use another docker image with a web server, for example : wordpress:6.0.2-apache.

If you would stick with PHP-FPM, you have to add a web server container to your stack, for example : nginx or apache (or what ever you like).

Traefik proxy is just a proxy, not a webserver.

I had the same problems when i started using traefik, it take some time to understand everything.
Don't give up it worth it :wink:

Bye

would i configure nginx as a service in the traefik stack, in the wordpress stack, or in its own separate stack? i'd tried doing that before, and failed. i've also tried using the default wordpress image that includes apache, which returns 404 errors instead of bad gateway.

Well, It's up to you to prefer including a webserver on your stack or outside (in it's own stack). Both will work.

Personaly, i prefer include all the stuff in one docker-compose file (although the database in it's own stack is a better practice).

There is my functionnal docker-compose file for a wordpress site (PHP-FPM) :

version: '3.7'
services:
  database:
    image: mariadb:${MARIADB_VERSION}
    container_name: ${PROJECT_NAME}_database
    restart: unless-stopped
    volumes:
      - ./conf/mysql/mysql.cnf:/etc/mysql/conf.d/mysql.cnf:ro
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - db:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_USER_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    networks:
      - net

  phpmyadmin:
    image: phpmyadmin:latest
    container_name: ${PROJECT_NAME}_phpmyadmin
    restart: unless-stopped
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    depends_on:
      - database
    environment:
      PMA_HOST: database
      UPLOAD_LIMIT: 1G
      PMA_PORT: 3306
      PMA_ABSOLUTE_URI: "https://${HOST_NAME}/_phpmyadmin/"
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.${PROJECT_NAME}-phpmyadmin.rule=Host(`${HOST_NAME}`) && PathPrefix(`/_phpmyadmin/`)
      - traefik.http.routers.${PROJECT_NAME}-phpmyadmin.tls=true
      - traefik.http.routers.${PROJECT_NAME}-phpmyadmin.entrypoints=http,https
      - traefik.http.routers.${PROJECT_NAME}-phpmyadmin.middlewares=stripprefix-phpmyadmin
      - traefik.http.routers.${PROJECT_NAME}-phpmyadmin.service=${PROJECT_NAME}-phpmyadmin
      - traefik.http.middlewares.stripprefix-phpmyadmin.stripprefix.prefixes=/_phpmyadmin
      - traefik.http.services.${PROJECT_NAME}-phpmyadmin.loadbalancer.server.port=80
    networks:
      - net
      - proxy


  nginx:
    image: nginx:${NGINX_VERSION}
    container_name: ${PROJECT_NAME}_nginx
    restart: unless-stopped
    volumes:
      - ./conf/nginx:/etc/nginx:ro
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./logs/nginx:/var/log/nginx
      - ./www:/var/www/html
    depends_on:
      - wordpress
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.${PROJECT_NAME}.rule=Host(`${HOST_NAME}`, `www.${HOST_NAME}`)
      - traefik.http.routers.${PROJECT_NAME}.entrypoints=http,https
      - traefik.http.routers.${PROJECT_NAME}.middlewares=compression@file,security@file
      - traefik.http.routers.${PROJECT_NAME}.service=${PROJECT_NAME}
      - traefik.http.services.${PROJECT_NAME}.loadbalancer.server.port=80
      - traefik.http.routers.${PROJECT_NAME}-admin.rule=Host(`${HOST_NAME}`) && (Path(`/wp-admin`) || Path(`/wp-login.php`))
      - traefik.http.routers.${PROJECT_NAME}-admin.entrypoints=http,https
      - traefik.http.routers.${PROJECT_NAME}-admin.middlewares=whitelist@file
      - traefik.http.routers.${PROJECT_NAME}-admin.service=${PROJECT_NAME}-admin
      - traefik.http.services.${PROJECT_NAME}-admin.loadbalancer.server.port=80
    networks:
      - proxy
      - net

  wordpress:
    image: wordpress:${PHP_VERSION}
    container_name: ${PROJECT_NAME}_wordpress
    restart: unless-stopped
    volumes:
      - ./conf/php/php-fpm.conf:/usr/local/etc/php-fpm.d/zzz-custom.conf:ro
      - ./conf/php/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./www:/var/www/html
    depends_on:
      - database
    environment:
      WORDPRESS_DB_HOST: database
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_USER_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      WORDPRESS_TABLE_PREFIX: ${MYSQL_TABLE_PREFIX}
    mem_limit: 1G
    extra_hosts:
      - ${HOST_NAME}:${DOCKER_GATEWAY_TRAEFIK_PROXY}
    networks:
      - net

volumes:
  db:

networks:
  net:
    driver: bridge
  proxy:
    external: true

There is a lots of tweak in my docker-compose file (php .ini, database config, middleware, etc), you don't have to use all of them (you can ignore them) :wink:

Maybe your problem come from your traefik's config...

Hope this help !

Bye

1 Like