frankenphp icon indicating copy to clipboard operation
frankenphp copied to clipboard

Required Caddy directories when running FrankenPHP inside Docker

Open lacatoire opened this issue 1 month ago • 7 comments

This PR adds missing documentation about two Caddy directories required when running FrankenPHP inside Docker:

  • /data/caddy
  • /config/caddy (used to store autosave.json)

These directories are not created by the official image. When `/config/caddy`` is absent or not writable, Caddy cannot autosave its adapted configuration and silently falls back to “serving initial configuration”, even with a valid Caddyfile.

What this PR adds

  • A Dockerfile snippet to create the required directories with proper permissions.
  • A note for Docker Compose users explaining why mounting the application over
  • /app can overwrite internal Caddy files.
  • A recommended alternative layout using /srv/app when bind-mounting the code.

This should make the Docker setup more reliable for developers using FrankenPHP.

lacatoire avatar Nov 27 '25 15:11 lacatoire

Hi, thanks for the PR. Can you share how to trigger this issue and what it causes?

henderkes avatar Nov 27 '25 17:11 henderkes

Hi @henderkes, sure here is how to reproduce it and what happens.

How to trigger the issue

  1. Use the official image with a custom Caddyfile:
FROM dunglas/frankenphp:php8.4-alpine
COPY Caddyfile /etc/caddy/Caddyfile
CMD ["frankenphp", "run", "--config=/etc/caddy/Caddyfile"]
  1. Run it in Docker Compose with a bind-mounted application directory:
volumes:
  - .:/app
  1. Start the container. Since /config/caddy does not exist in the base image, Caddy cannot write its autosave file (autosave.json).

What it causes

Depending on the user running Caddy, the log shows either:

open /config/caddy/autosave.json: permission denied or open /config/caddy/autosave.json: no such file or directory

In both cases, Caddy discards the adapted configuration and falls back to: serving initial configuration even though the provided Caddyfile is valid.

Creating /config/caddy (and /data/caddy for TLS/locks) resolves the issue.

lacatoire avatar Nov 27 '25 18:11 lacatoire

Thank you! This sounds a lot like something that we should resolve internally, rather than depending on our users to jump through those hoops. I'll give it a shot reproducing it after SymfonyCon to figure out how to fix it.

henderkes avatar Nov 27 '25 18:11 henderkes

I think this is partly explained in the Running as non-root user section. /config/caddy and /data/caddy are owned by root. I'd not be against giving them to www-data by default though. We could also add a frankenphp/caddy user (id:1000) and recommend running the server as that user.

AlliBalliBaba avatar Nov 28 '25 09:11 AlliBalliBaba

I'd not be against giving them to www-data by default though. We could also add a frankenphp/caddy user (id:1000) and recommend running the server as that user.

The problem with that approach is that it doesn't work in Kubernetes and can't be run. We used to do this back in the day and flipped flopped between different configurations until we landed on the one we have. There's some really old issues about it that could probably be dug up from when we had very simple Dockerfiles.

withinboredom avatar Nov 28 '25 09:11 withinboredom

Did you encounter other issues with Kubernetes? From my experience it should be enough that the user running supervisor/caddy/php owns all the directories that it will be writing to. Also, the 80/443 ports need to be accessible to the server process (RUN setcap CAP_NET_BIND_SERVICE=+eip name-of-binary).

The same logic applies when hardening the image with a read-only root filesystem or so.

AlliBalliBaba avatar Nov 28 '25 12:11 AlliBalliBaba