Dockerized Wordpress + Discourse + Caddy

I recalled mention of Acme v2 in the 0.10.12 release blog post. What threw me was that it was the “Do you agree to the terms? (y/n)” prompt. It allowed entering “y”, but would fail anyway. Oh well, it didn’t take me tooooooo long to figure it out. :smile:

Any guidance on the proper Caddyfile syntax for the fpm-alpine variants of the Wordpress Docker images?

I started with the Wordpress Caddyfile example and tried a few variations which all yield 502s.

I read through 10 or so forum posts that kinda sorta deal with similar issues, but wasn’t able to piece it together.

Add errors stdout to your Caddyfile and let us know what comes up when you get a 502.

I tore this down for now. Is there a working Caddyfile example somewhere that uses Docker (abiosoft/caddy) and fpm? I’m happy to log errors when I rebuild, but a known-to-be-working example would help too.

This is known good:

docker-compose.yml
version: '3'

services:
  caddy:
    image: abiosoft/caddy:latest
    command: ["-log", "stdout", "-agree",
      "-email", "letsencrypt@whitestrake.net",
      "-conf", "/etc/Caddyfiles/Caddyfile"]
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./certificates:/root/.caddy
      - ./caddyfiles:/etc/Caddyfiles
      - ./keys:/root/keys
      - ./sites:/srv
    restart: always

  php-fpm:
    build: ./php-dockerfile
    volumes:
      - ./sites:/srv
    restart: always
php-dockerfile/Dockerfile
FROM php:fpm-alpine
RUN apk add --no-cache libpng-dev
RUN docker-php-ext-install gd zip
caddyfiles/Caddyfile
http://www.whitestrake.net, https://www.whitestrake.net {
  redir https://whitestrake.net{uri}
}

whitestrake.net {
  root /srv/whitestrake
  git {
    repo git@gitlab.com:Whitestrake/whitestrake-home.git
    key /root/keys/id_rsa
    hook /c3b9edf5e8609ca3419c4a007448cd8d
  }
  gzip
  push
  fastcgi / php-fpm:9000 php
}
2 Likes

I made some headway on this. I think the 502 problem was that I was targeting the external directory of the bind-mount. Those errors are gone, but it’s not exactly working.

The Good

wp-main_1          | [23-Apr-2018 00:02:50] NOTICE: fpm is running, pid 1
wp-main_1          | [23-Apr-2018 00:02:50] NOTICE: ready to handle connections

The Bad

When accessing https://blog.example.com I get Access denied. in the browser, and this in terminal:

403 security.limit_extension errors
wp-main_1          | 172.xx.xx.xx -  23/Apr/2018:00:14:00 +0000 "GET /" 403
wp-main_1          | [23-Apr-2018 00:14:00] WARNING: [pool www] child 57 said into stderr: "NOTICE: Access to the script '/var/www/html' has been denied (see security.limit_extensions)"
caddy_1            | 23/Apr/2018:00:14:00 +0000 [ERROR 0 /] Access to the script '/var/www/html' has been denied (see security.limit_extensions)

Then if I manually enter https://blog.example.com/wp-admin/install.php, I get an html-only page. There’s the expected content, but no .css or .js niceness. This is accompanied by this output:

CSS and JS security.limit_extensions errors
wp-main_1          | 172.xx.xx.xx -  23/Apr/2018:00:16:00 +0000 "GET /" 403
caddy_1            | 23/Apr/2018:00:16:00 +0000 [ERROR 0 /wp-includes/css/dashicons.min.css] Access to the script '/var/www/html/wp-includes/css/dashicons.min.css' has been denied (see security.limit_extensions)
caddy_1            | 23/Apr/2018:00:16:00 +0000 [ERROR 0 /wp-includes/js/jquery/jquery-migrate.min.js] Access to the script '/var/www/html/wp-includes/js/jquery/jquery-migrate.min.js' has been denied (see security.limit_extensions)
wp-main_1          | 172.xx.xx.xx -  23/Apr/2018:00:16:00 +0000 "GET /" 403
wp-main_1          | [23-Apr-2018 00:16:00] WARNING: [pool www] child 57 said into stderr: "NOTICE: Access to the script '/var/www/html/wp-admin/js/language-chooser.min.js' has been denied (see security.limit_extensions)"
caddy_1            | 23/Apr/2018:00:16:00 +0000 [ERROR 0 /wp-admin/js/language-chooser.min.js] Access to the script '/var/www/html/wp-admin/js/language-chooser.min.js' has been denied (see security.limit_extensions)

The Ugly

docker-compose.yml
version: '3.2'

services:
  wp-main:
    image: registry.gitlab.com/jetatomic/wordpress
    depends_on:
      - main-db
    environment:
      WORDPRESS_DB_PASSWORD: n0ne5hallp@55
      WORDPRESS_DB_HOST: main-db
      WORDPRESS_DB_USER: wpdrone
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - /var/www/main.wp:/var/www/html
    restart: always

  main-db:
    image: mariadb
    environment:
      MYSQL_ROOT_PASSWORD: n0ne5hallp@55
      MYSQL_USER: wpdrone
      MYSQL_PASSWORD: n0ne5hallp@55
      MYSQL_DATABASE: wordpress
    volumes:
      - /var/farm/mysql/main-db:/var/lib/mysql
    restart: always

    caddy:
      image: abiosoft/caddy:0.10.12
      command: ["-email", "letsencrypt@example.com",
        "-conf", "/etc/Caddyfile",
        "-agree"]
      ports:
        - 80:80
        - 80:80/udp
        - 443:443
        - 443:443/udp
      volumes:
        - /var/farm/caddy/Caddyfile:/etc/Caddyfile
        - /var/farm/caddy:/root/.caddy
        - /var/discourse/shared/standalone:/sock
      restart: always
Caddyfile
blog.example.com {
  root /var/www/html
  gzip
  push
  errors stdout
  fastcgi / wp-main:9000
}
Dockerfile
FROM wordpress:4.9.5-php7.2-fpm-alpine

RUN apk add --no-cache \
  libpng-dev \
	wget \
	unzip \
	nano \
	sudo \
	&& docker-php-ext-configure gd --with-png-dir=/usr \
  && docker-php-ext-install gd zip 

  # Then more custom config junk

Since the security.limit_extensions is a PHP-FPM thing, I suspect there’s a package or config missing in the Dockerfile. Rewrite? Preliminary googling shows methods for tweaking that extension, but everything so far looks sketchy security-wise.

So I’ll keep researching, and update if I get a solution. Until then, I’m open to suggestions.

Edit: Related thread, but no clear solution.

Change this line from your Caddyfile:

fastcgi / wp-main:9000

to:

fastcgi / wp-main:9000 php

otherwise, all requests will be proxied to the PHP-PFM listener (not just .php files).

I believe the security limit_extensions errors are appearing when requests for non-PHP indexes or static files are received, which should be handled directly by Caddy and not by the PHP-FPM listener.

Yes, I had it that way before and after posting that Caddyfile example. I guess I just copy/pasted it between tests. The behavior above happens with php added to the line.

I understand what those words mean, but I don’t know how to effect that in Caddyspeak (or Nginx, for that matter, though there are a bunch of examples of Nginx rewrites for WP).

The PHP preset should handle that - only PHP scripts should be sent when the preset is added. Here’s what php does:

ext   .php         # specifies extensions which will get proxied
split .php         # tells Caddy that anything after this goes in PATH_INFO
index index.php    # specifies a default file to try for an index request

So you can see with it enabled (or these settings manually specified - php is just shorthand), no non-PHP file should ever be sent via FastCGI.

Can you double check it’s present in the Caddyfile, restart Caddy, and let me know if the CSS and JS requests still get sent to PHP?

Added this from this write-up:

  rewrite {
      if {path} not_match ^\/wp-admin
      to {path} {path}/ /index.php?_url={uri}
  }

Doesn’t resolve the security error, but it helps with the redirect from https://blog.example.com

I’ve seen the form /index.php?_url={uri} before, but it seems to be a case of some form of citogenesis - I’ve never found in the WordPress documentation that this query parameter should be specified, and don’t believe it has any particular effect. Whenever I use WordPress that isn’t simply the official Docker WordPress image, I just rewrite to {path} {path}/ /index.php?{query}, which works fine.

Caddyfile:

    blog.example.com {
      root /var/www/html
      gzip
      push
      errors stderr
      log stdout
      fastcgi / wp-main:9000 php
    }

You’re right, it looks like the security.limit_extensions errors are gone and replaced by 200s (yay) 404s (boo). This is what I’m seeing currently:

caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:10 +0000] "GET / HTTP/1.1" 404 38
wp-main_1          | 172.xx.xx.xx -  23/Apr/2018:03:52:23 +0000 "GET /wp-admin/install.php" 200
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:23 +0000] "GET /wp-admin/install.php HTTP/1.1" 200 4004
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:24 +0000] "GET /wp-admin/css/install.min.css?ver=4.9.5 HTTP/1.1" 404 38
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:24 +0000] "GET /wp-includes/css/dashicons.min.css?ver=4.9.5 HTTP/1.1" 404 38
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:24 +0000] "GET /wp-includes/css/buttons.min.css?ver=4.9.5 HTTP/1.1" 404 38
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:24 +0000] "GET /wp-includes/js/jquery/jquery.js?ver=1.12.4 HTTP/1.1" 404 38
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:24 +0000] "GET /wp-admin/js/language-chooser.min.js?ver=4.9.5 HTTP/1.1" 404 38
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:03:52:24 +0000] "GET /wp-includes/js/jquery/jquery-migrate.min.js?ver=1.4.1 HTTP/1.1" 404 38

Isn’t the rewrite supposed to go there?

rewrite {
  to {path} {path}/ /index.php?{query}
}
Caddyfile
blog.example.com {
  root /var/www/html
  gzip
  push
  errors stderr
  log stdout
  fastcgi / wp-main:9000 php
  rewrite {
    to {path} {path}/ /index.php?{query}
  }
}

With that bit in there I get a screen full of 302 errors:

wp-main_1          | 172.xx.xx.xx -  23/Apr/2018:05:27:55 +0000 "GET /index.php" 302
caddy_1            | 172.xx.xx.xx - - [23/Apr/2018:05:27:55 +0000] "GET /wp-admin/install.php HTTP/1.1" 302 23

302s aren’t errors, they’re redirections - specifically, non-cache-able redirections.

Since there are zero redirections in your Caddyfile, the only source of a redirection would logically be the PHP script itself, which seems to indicate that the FastCGI is working, at least. Try a curl -IL example.com and look for a Location header in the response.

HTTP/1.1 302 Found
Cache-Control: no-cache, must-revalidate, max-age=0
Content-Type: text/html; charset=UTF-8
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Location: https://blog.example.com/wp-admin/install.php
Server: Caddy
Status: 302 Found
X-Powered-By: PHP/7.2.4

Looks good; seems to be redirecting to first-time setup. Now follow the chain; try curl -IL https://blog.example.com/wp-admin/install.php?

Results from that query are identical. Isn’t it somewhat of a step backwards since the first-time setup page was loading before (though the CSS and JS was 404)?

This gets rid of the 302 for the /wp-admin/install.php, but still throws 302s and 404s for the CSS and JS in /wp-include:

  rewrite {
      if {path} not_match ^\/wp-admin
      to {path} {path}/ /index.php?{query}
  }
caddy_1            | xx.xx.xx.xx - - [23/Apr/2018:06:11:04 +0000] "GET /wp-admin/css/install.min.css?ver=4.9.5 HTTP/2.0" 404 38
wp-main_1          | xx.xx.xx.xx -  23/Apr/2018:06:11:04 +0000 "GET /index.php" 302
caddy_1            | xx.xx.xx.xx - - [23/Apr/2018:06:11:04 +0000] "GET /wp-includes/css/dashicons.min.css?ver=4.9.5 HTTP/2.0" 302 23

Yep, looks like that rewrite is causing some issues. It might be rewriting to index.php when it should be just serving the file off disk?

What do you get from ls -l /var/www/html/wp-includes/css/dashicons.min.css on the Caddy host?