This is a “clone and go” docker stack where you can setup a Caddy instance and host a Wordpress site in a few minutes.
Docker
and compose
files are separated from application
config. You don’t need to understand the setup to get going, even do I encourage people to actually understand what they are using.
Config Injection
Configuration is injected into the docker containers as environment variables. You should have as little config as possible injected into each container to lower the attack vector. Therefor it is better to have many small config files than one large and then inject only what is needed for that application to run.
I will store all config files in the conf
directory.
Database
Yes, we need a DB and I will use MariaDB. Let’s make a conf/db.env
file and a docker-compose.yml
.
conf/db.env
:
MYSQL_USER=siteuser
MYSQL_PASSWORD=sitepassword
MYSQL_DATABASE=wordpress
MYSQL_ROOT_PASSWORD=rootpassword
TERM=meh
MariaDB and other applications are mapping the Mysql environment variables and even though this is MariaDB, the variables start with MYSQL
.
These are start values that will give us a database during startup. The TERM
can be anything, as long as it is set it will be accepted.
The docker-compose.yml
:
version: "3.9"
services:
db:
image: mariadb:11-jammy
restart: always
volumes:
- dbdata:/var/lib/mysql
env_file:
- ./conf/db.env
logging:
driver: "json-file"
options:
max-size: "1M"
max-file: "10"
volumes:
dbdata:
driver: local
Wordpress
Let’s extend the composition with a Wordpress instance.
conf/wordpress.env
:
WORDPRESS_DB_USER=siteuser
WORDPRESS_DB_PASSWORD=sitepassword
WORDPRESS_DB_NAME=wordpress
WORDPRESS_DB_HOST=db:3306
PHP_MEMORY_LIMIT=2048M
PHP_ENABLE_XDEBUG=false
DEBUG=false
UPLOAD_MAX_FILESIZE=64M
The bottom part is optional, but gives a bit of control over the container.
Extending the docker-compose.yml
:
wp-fpm:
image: wordpress:6-fpm
restart: always
links:
- db
depends_on:
- db
volumes:
- html:/var/www/html
env_file:
- ./conf/wordpress.env
logging:
driver: "json-file"
options:
max-size: "1M"
max-file: "10"
volumes:
html:
driver: local
driver_opts:
type: none
device: $PWD/html
o: bind
dbdata:
driver: local
Create an html
directory where you have the docker-compose.yml
. This dir will act as a mounted volume inside the WP container and will give you direct access to all the site files when up and running.
The fun Part, Adding Caddy!
So this will be bit more complex than just adding an image to the compose file. Adding it this way makes it easy to run the same setup locally, as a test server or as a prod setup. The Caddyfile
itself will be pretty static and we will inject extra stuff with our config files.
Let’s create a caddy/etc/Caddyfile
:
{
default_sni {$SERVER_NAME}
}
{$SERVER_NAME} {
import /etc/{$TLS_MODE}
root * /var/www/html
encode zstd gzip
@forbidden {
not path /wp-includes/ms-files.php
path /wp-admin/includes/*.php
path /wp-includes/*.php
path /wp-config.php
path /wp-content/uploads/*.php
path /.user.ini
path /wp-content/debug.log
}
respond @forbidden "Access denied" 403
php_fastcgi wp-fpm:9000
file_server
log {
output file /var/log/caddy.log
}
header / {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
}
Also, let’s create two more files in the same directory.
caddy/etc/tls_auto
:
tls {$TLS_AUTO_EMAIL}
And caddy/etc/tls_selfsigned
:
tls internal
And then our conf/caddy.env
:
# replace this with your FQDN if this is a public server
SERVER_NAME=localhost
# replace this with tls_auto if this is a public server
TLS_MODE=tls_selfsigned
TLS_AUTO_EMAIL=your@email.com
As you can see above there is a correlation between the env variables, conf files and the Caddyfile. If you set the TLS_MODE
to tls_auto
, the other file will be imported into Caddyfile
and Caddy will try to fetch a certificate based on your server name.
Adding on to the docker-compose.yml
file:
...
caddy:
image: caddy:2.7-alpine
restart: always
volumes:
- ./html:/var/www/html
- ./.caddy_data:/data
- ./.caddy_config:/config
- ./caddy/etc/Caddyfile:/etc/caddy/Caddyfile
- ./caddy/etc/tls_auto:/etc/tls_auto
- ./caddy/etc/tls_selfsigned:/etc/tls_selfsigned
ports:
- "80:80"
- "443:443"
- "443:443/udp"
env_file:
- ./conf/caddy.env
logging:
driver: "json-file"
options:
max-size: "1M"
max-file: "10"
So all the files we’ve setup for Caddy are added onto the container as volumes. The env file is injecting its content as environment variables.
You can now change the conf
files and restart to get a new behavior without touching the real application config.
With all this created, we have the last step:
docker compose up -d
Open up your favorite browser and go to https://localhost
and you should get to the final steps of completing Wordpress.
If you want to try this setup without the manual copy pasting, you can find a complete working example in this branch:
if you want to dig deeper, with support for more features. Check the main branch of the same repository: