Caddy es un servidor web muy versátil y sobre todo muy fácil de usar ya que sus archivos de configuración están pensados para ser extremadamente simples y autoexplicativos. Por defecto Caddy puede servir páginas estáticas y redireccionar hacia otros servicios a modo de proxy inverso. Esto hace que sea muy llamativo para usar con contenedores Docker.
Caddy no pude de forma nativa interpretar código PHP pero si que contiene directivas que nos permiten redireccionar el tráfico de paginas PHP a través de fast_cgi hacia php-fpm. A continuación te explico como:
Configuración con Docker Compose
version: "3.7"
services:
caddy:
container_name: caddy
image: caddy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- TZ=Europe/Madrid
volumes:
- ./caddyFile:/etc/caddy/
- ./site:/srv
- ./data:/data
- ./config:/config
- ./log:/log
networks:
- frontend
php:
container_name: phpfpm
image: docker.io/bitnami/php-fpm:7.4
restart: unless-stopped
environment:
- TZ=Europe/Madrid
volumes:
- ./site/:/app/
networks:
- frontend
- mysql
networks:
frontend:
external: true
mysql:
external: true
En el docker-compose.yaml podemos ver como se definen los 2 servicios: Caddy y phpfpm.
En el caso de caddy, mapeamos los siguientes volúmenes a directorios del sistema. Todos estos directorios deben ser creados antes de iniciar el contenedor.
- caddyFile: es el directorio donde se guardará el archivo Caddyfile que contiene toda la configuración de Caddy. Debemos crear tanto el directorio como el priopio archivo Caddyfile en su interior antes de inicial el contenedor.
- site: es el directorio donde se guardarán los archivos estáticos y php que serán servidos por Caddy.
- data: este directorio contiene datos de ejecución de Caddy, como el estados de los diferentes endpoints, los certificados, etc..
- config: contiene configuración avanzada de caddy. No lo usaremos.
- log: va a contener los logs de Caddy
El en el caso del contenedor de phpfpm podemos ver que el único volumen mapeado es site, si nos fijamos, este directorio apunta a la misma ruta de la máquina host que el mismo directorio site del contenedor de Caddy. Esto es importante. Además, nos fijamos que dentro del contenedor se mapea como /app, mientras que en Caddy se mapea como /srv. Recuerda esto porque tendremos que volver a ello más adelante.
Por último simplemente comentar que en este ejemplo se han definido dos redes externas, frontend y mysql. La red frontend será la usada para comunicar Caddy con phpfpm y con el resto de los contenedores a los que va a enviar tráfico como proxy inverso, y mysql será una red usada por los scripts de php ejecutados dentro de phpfpm que quieran acceder a base de datos.
Configuración de caddyFile
example.com {
root * /srv/example.com
file_server
redir /wiki /wiki/
handle_path /wiki/* {
reverse_proxy http://mywiki
}
log {
output file /log/example.com.log
}
tls webmaster@example.com
}
example.org {
root * /srv/example.org
file_server
php_fastcgi phpfpm:9000 {
root /app/example.org/
}
log {
output file /log/example.org.log
}
tls webmaster@example.org
}
En este fichero Caddyfile de ejemplo, se han definido dos sitios web. El primero consiste en una página estática con una redirección mediante un proxy inverso a otro contenedor de docker, en este caso para servir una wiki. Pero a nosotros nos interesa el segundo sitio example.org.
En primer lugar podemos ver que se define como root del sitio /srv/example.org esto quiere decir que los ficheros se van a ir a buscar al directorio mapeado a ./site/ del host, y se indica que se sirvan archivos estáticos de ese directorio mediante la directiva: file_server.
En segundo lugar la directiva php_fastcgi será la encargada de enviar todas las peticiones hacia archivos .php hacia nuestro contenedor phpfpm. Dentro de esta directiva podemos ver que se modifica el root; esto es debido precisamente a lo que comentamos en el apartado anterior: para Caddy el root es /srv/example.org, pero dentro del contenedor de phpfpm, la misma ruta esta mapeada como /app/example.org. Por este motivo tenemos que redefinir la propiedad root dentro de la directiva de php_fastcgi. Otra opción sería modificar las configuraciones avanzadas de uno u otro contenedor para que ambas rutas tuviese exactamente el mismo nombre.
Con esta configuración, cuando caddy reciba una petición, por ejemplo, al archivo /srv/oshwdem.org/index.php, la va a redireccionar hacia phpfpm renombrando la ruta a /app/oshwdem.org/index.php y phpfpm podrá interpretar el archivo php y ejecutarlo.
Usuarios y permisos
Por desgracia todavía no hemos acabado. Caddy usa por defecto el usuario root con uid 0 y phpfpm utiliza el usuario daemon con uid 1 y probablemente nosotros estemos usando un tercer usuario con uid 1000 o similar. Como Caddy usa root, no vamos a tener problema ya que va a poder leer los ficheros independientemente de los permisos que pongamos, por ello, tenemos que centrarnos en phpfpm.
chown -R daemon:daemon example.org
Con este comando vamos a asignar los archivos de la página al usuario con uid 1. En este caso, como la máquina host es debian, se corresponde con el usuario daemon, sin embargo si estamos usando un sistema basado en redhat u otros, esto puede no ser así. En cualquier caso tenemos que asignarle los ficheros al usuario con uid 1 phpfpm pueda acceder a ellos correctamente.
Una vez hecho esto, podemos asignar unos permisos con cierta seguridad a toda la carpeta. En concreto vamos a asignar los permisos 644 a todos los ficheros y los permisos 750 a todos los directorios. Podemos hacerlo fácilmente con los siguientes comandos.
sudo find -type f -exec chmod 644 {} \;
sudo find -type d -exec chmod 750 {} \;
Una vez hecho esto, podemos ejecutar nuestro docker compose con docker-compose up -d y deberíamos poder acceder a nuestra web con php sin problemas.
Errores comunes
File not found
Uno de los errores mas comunes que nos podemos encontrar es File not found. Este error quiere decir que phpfpm no ha podido leer el archivo que hemos solicitado en nuestro navegador, o bien porque no existe o bien porque los permisos no son correctos.
Como vimos en el apartado de configuración de caddyFile, cuando Caddy recibe una petición php la redirige a phpfpm con la ruta completa del sistema. Por este motivo tenemos que incluir la directiva root dentro de php_fastcgi para renombrar dicha ruta. Asegurate de que estas rutas son correctas. Además revisa los permisos de los ficheros así como de los directorios que están mapeados. El usuario que ejecuta el docker compose debe tener permisos para mapear los directorios dentro del contenedor y dentro del directorio el usuario con uid 1 debe tener permisos de al menos lectura de todos los ficheros y directorios.