Running Wordpress with php 8, docker-compose, and traefik

This is an update of a previous post on running a fresh Wordpress installation with Docker. Details on the docker-compose.yml in that post remain basically accurate, but I’m reporting here an updated Dockerfile based on php 8, as well as some quick fixes for common problems.

Dockerfile ready for Wordpress based on Php 8 (php:8.0-apache)

N.B. This is a basic Docker image that comes with an empty server directory, no Worpdress pre-installed. It just comes with the main things required by a fresh Wordpress install.

FROM php:8.0-apache

RUN apt-get update

RUN apt-get install -y libzip-dev zlib1g-dev 

#imagick
RUN apt-get -y install gcc make autoconf libc-dev pkg-config
RUN apt-get -y install libmagickwand-dev --no-install-recommends
RUN pecl install imagick
RUN docker-php-ext-enable imagick

RUN docker-php-ext-install zip mysqli pdo pdo_mysql gd exif

RUN rm -r /var/lib/apt/lists/*

RUN a2enmod rewrite

RUN sed -i -e 's/Listen 80/Listen 80\nServerName localhost/' /etc/apache2/ports.conf

RUN service apache2 restart

EXPOSE 80

Fix ownership on the server after having installed Wordpress

After you have manually installed Wordpress, you may need to fix ownership of the wp-content folder. The need may appear, for example, if Wordpress asks for FTP credentials in order to install plugins.

docker exec -u root -it {CONTAINER_ID} /bin/bash
chown -R www-data wp-content
chmod -R 755 wp-content

This is the most secure option, but then you will need to relax ownership when updating.

Relax ownerships:

chown www-data:www-data  -R *

Update. And then set them back.

chown root:root  -R * 
chown www-data:www-data wp-content

See this question on StackOverflow for further references and debates on the implications. Notice that if you keep the more restrictive file ownership proposed above automatic updates will not work.

To prevent Wordpress from asking about FTP credentials, you may also want to add the following line to wp-config.php:

define('FS_METHOD', 'direct');

Keep in mind that if you are using Traefik or similar reverse proxy, as described in the Wordpress documentation you need to add the following chunk at the beginning of your wp-config.php file.

define('FORCE_SSL_ADMIN', true);
// in some setups HTTP_X_FORWARDED_PROTO might contain 
// a comma-separated list e.g. http,https
// so check for https existence
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
$_SERVER['HTTPS']='on';

Docker compose based on Traefik for Wordpress

Finally, here’s again a template for docker-compose.yml based on Traefik that should get you up and running if you have already pointed your A records to your server. (phpmyadmin can of course be safely removed).

version: "3.3"

services:

  traefik:
    image: "traefik:v2.8"
    container_name: "traefik"
    restart: always
    command:
      #- "--log.level=DEBUG"
      - "--api"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge=true"
      - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge.entrypoint=web"
      #- "--certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.myhttpchallenge.acme.email=youremail@example.com"
      - "--certificatesresolvers.myhttpchallenge.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    networks:
      - network1
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    labels:
      - "traefik.enable=true"
      # global redirect to https
      - "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.redirs.entrypoints=web"
      - "traefik.http.routers.redirs.middlewares=redirect-to-https"
      # middleware redirect
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      # dashboard
      - "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.tls.certresolver=myhttpchallenge"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.middlewares=authtraefik"


  example_com_db:
    image: mysql:5.7
    volumes:
      - example_com_db:/var/lib/mysql
    restart: always
    networks:
      - network1
    environment:
      MYSQL_ROOT_PASSWORD: Ez2oogha5ogh4woh
      MYSQL_DATABASE: ieseehahF0oo
      MYSQL_USER: ii4eshaeZ9ta
      MYSQL_PASSWORD: lae7gia4MohW

  example_com:
    depends_on:
      - example_com_db
    image: giocomai/php_8_0:latest
    networks:
      - network1
    volumes:
      - example_com:/var/www/html/
    ports:
      - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: example_com_db:3306
      WORDPRESS_DB_NAME: ieseehahF0oo
      WORDPRESS_DB_USER: ii4eshaeZ9ta
      WORDPRESS_DB_PASSWORD: lae7gia4MohW
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.example_com.rule=Host(`test.example.com`)"
      - "traefik.http.routers.example_com.entrypoints=websecure"
      - "traefik.http.routers.example_com.tls.certresolver=myhttpchallenge"

  phpmyadmin:
    depends_on:
      - example_com_db
    image: phpmyadmin/phpmyadmin:latest
    restart: always
    links:
      - example_com_db:db
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.phpmyadmin.rule=Host(`phpmyadmin.example.com`)"
      - "traefik.http.routers.phpmyadmin.entrypoints=websecure"
      - "traefik.http.routers.phpmyadmin.tls.certresolver=myhttpchallenge"

volumes:
  example_com:
  example_com_db:
  
Avatar
Giorgio Comai
Researcher, data analyst

Related