Automated Permissions Fixing? [PHP] [Docker]

Hey folks,

I’m setting up a few sites running Grav as CMS, inside the abiosoft/caddy Docker container. Regardless of the method I use to drop caddy into the Caddy site root, (unzip, git clone, composer) there are always permissions problems - these seem to be because the abiosoft container runs the php-fpm pool workers as the user ‘nobody’. I’ve written a script to correct them, but it’s clumsy and cumbersome.

Am I doing it wrong?

Does anyone have tips to streamline this process? I thought about rolling my own Dockerfile and using some tool like gosu or S6 overlay, or even running a separate PHP-FPM container, but these are non-trivial and require ongoing maintenance. So I guess my question is, is this a solved problem and I just don’t know the solution yet?

I don’t believe that’s a decision made by @abiosoft - as far as I can tell that’s probably just the default for the php7-fpm package on Alpine Linux.

You could run a script on startup in your Caddyfile, to sed the /etc/php7/php-fpm.conf file with the correct user and group.

Alternately, use a PHP container. It’s not difficult at all. Here’s one of my setups:

version: '3'

    image: abiosoft/caddy:latest
    command: ["-log", "stdout",
      "-email", "",
      "-conf", "/etc/Caddyfiles/Caddyfile"]
      - "80:80"
      - "443:443"
      - ./certificates:/root/.caddy
      - ./caddyfiles:/etc/Caddyfiles
      - ./sites:/srv
    restart: unless-stopped

    image: php:7-fpm-alpine
      - ./sites:/srv
    restart: unless-stopped

With the setup above, PHP is as simple as fastcgi / php-fpm:9000 php.


Just to add onto the example @Whitestrake gave you, you may run into issues and hit LetsEncrypt rate limits if your domain is misconfigured, if you use a restart policy like restart: unless-stopped.

See the details I posted here: Problem when using a restart policy if Caddy errors out · Issue #65 · abiosoft/caddy-docker · GitHub

You can use that shell script, name it and put it beside the following Dockerfile and you can build from that instead.


FROM abiosoft/caddy:latest

COPY /usr/bin/

ENTRYPOINT ["/usr/bin/"]
CMD ["/usr/bin/caddy", "--conf", "/etc/Caddyfile", "--log", "stdout"]


# The file that tracks if this container has been started with errors

# Implement a reset command to remove the errorfile
if [ "$1" = 'reset' ]; then
    rm -f "$errorfile"
    exit 0

# Caddy is being run normally
if [ "$1" = '/usr/bin/caddy' ] || [ "$1" = 'caddy' ]; then

    # Quit early if the error file exists
    # We do this so that we don't hit Let's Encrypt rate limits
    # if we have startup errors
    if [ -e "$errorfile" ]; then
        echo "Exiting, we previously ran with an error."
        exit 1

    # Run caddy

    # Get caddy exit code

    # Error 1 is a startup error, other exit codes don't concern us
    # See
    if [ $cmdoutput -eq 1 ]; then
        echo "Exited with error $cmdoutput."
        touch "$errorfile"

    # Exit with the same status code as caddy did
    exit "$cmdoutput"

# If the command is not caddy, we'll just run it normally
exec "$@"

To recap, what the shell script does is prevent Caddy from starting if there’s a haserror file, which should be persisted if you properly have your /root/.caddy volume set up. This file gets created if the shell script detects Caddy had an error during startup (indicated by exit code 1).

You can check if you hit the limit with the docker log command, it’ll show a message like “Exiting, we previously ran with an error.” If that happens and you know your DNS and whatnot is now properly configured, you can delete the haserror file from the volume, or you can run this command …

docker-compose run --rm --no-deps caddy reset

… which tells the entrypoint shell script to delete the file. Useful for scripting stuff yourself with uptime checks against the container.


Looking at the official php:fpm (at least the alpine variant) the default user is www-data, which works better, However, that image is missing a bunch of php libs like GD and zip, and my early attempts to add them in on startup based on docs hit some dependency hell. I’ll continue to hack at it.

My LE config lives in my Traefik RP, I try to keep Caddy’s config focused on serving sites. I’m not sure what best practice is here, if it makes more sense to do acme stuff in Caddy instead. As I’m running atop a Swarm cluster, Traefik seems better equipped for operating in this environment (with Consul as KV store for my Acme certs and associated config).

Ahh, unfortunate. If it’s not turn-key, it would probably end up being faster just to work out a bash script with a few seds to run on startup.

Haha, nope, looks like you’ve got it sorted. Traefik is great - some feature overlap with Caddy, but it’s really built for exactly what you’re doing. Caddy doesn’t do Consul for ACME storage, for one!

I built my own image based on php-fpm’s official image, and it’s much happier now. I’ll be iterating on this to add in a compiled version of Caddy including git rather than the alpine packaged version, probably using @abiosoft’s caddy:builder intermediate step at the beginning of my Dockerfile.

1 Like

That’s a nice looking Dockerfile!

All the regex rewrites in the Caddyfile make me a little sad when all those things could be checked with substrings, but I think that was just how the Grav team wrote their recommended Caddyfile. Oh well.

Glad you were able to get it working. Part of the goal of the repo and Dockerfile is to make it easy for people to extend it for their use cases.

1 Like

Thanks all, I made another update to move all the Grav specific stuff out of the Dockerfile, as it’s far easier to keep the image more generic and do all the Grav stuff as part of the Caddy Git deploy steps - I can pull from Git, run the composer and grav install scripts, and fix up permissions without having to roll another dockerfile, and am able to simply reconfigure with a different Caddyfile per environment. Lovely! Now the only real difference between my image and yours @abiosoft is the user php-fpm pool processes run as, which are just more friendly to Grav’s permissions when running as www-data instead of nobody.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.