How to get clean URLs (no .php extension)

1. The problem I’m having:

I want to serve .php files without the .php extension in the URL.

3. Caddy version:

v2.8.4

4. How I installed and ran Caddy:

I’m using Caddy from the Docker Image with Docker Compose, running it in Podman through podman-docker on my local machine for development. I want to eventually use a very similar configuration for a production server.

In compose.yml, I’m using this image:

image: docker.io/caddy:latest

a. System environment:

Docker Compose, caddy:latest, which is Alpine.

d. My complete Caddy config:

localhost

root * /var/www/pub
encode zstd gzip
log

php_fastcgi php:9000 {
        root /app
}

file_server

5. Links to relevant resources:

I found some older threads talking about ext but that directive doesn’t seem to exist in Caddy 2.

See php_fastcgi (Caddyfile directive) — Caddy Documentation, it explains how the directive works. Modern PHP frameworks have an index.php in the webroot which is used as the routing entrypoint for the PHP app. All requests that don’t match a file that exists on disk will be routed to your index.php file to handle the request.

Setting your PHP root to /app doesn’t really make sense. What are you trying to achieve with that? You already set the root with the root directive earlier in your config which is sufficient.

1 Like

I initially thought I would use plain PHP in development when I asked this question which wouldn’t have used this routing method, but I decided to use Symfony instead for a few reasons, so yes, fair enough.

Apologies in advance, but this question kind of opens a can of worms…

The simple answer is, “Because it doesn’t work unless I have it there. I just get file not found.” The documentation also says:

root sets the root folder to the site. It’s recommended to always use the root directive in conjunction with php_fastcgi, but overriding this can be useful when your PHP-FPM upstream is using a different root than Caddy (see an example).

I’m using Docker Compose with PHP, Caddy, and MySQL containers. It looks like this:

compose.yml
services:
  web:
    image: docker.io/caddy:latest
    ports:
      - 8080:80
      - 4430:443
    volumes:
      - ./src:/var/www/pub
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy_data:/data
      - ./caddy_config:/config
    networks:
      - nw
    depends_on:
      db:
        condition: service_healthy
      php:
        condition: service_started

  php:
    build: .
    volumes:
      - ./src:/app
    networks:
      - nw
    depends_on:
      db:
        condition: service_healthy

  db:
    image: docker.io/mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
      timeout: 5s
      retries: 60
    volumes:
      - ./db_data/db:/var/lib/mysql
      - ./db_data/dump:/backup
    environment:
      - MYSQL_ROOT_PASSWORD=securepassword
    networks:
      - nw

networks:
  nw:

My PHP Dockerfile looks like this:

PHP Dockerfile
FROM php:fpm-alpine

RUN apk add php-ctype php-iconv pcre php-session php-xml php-tokenizer php-dom composer

COPY src /app
WORKDIR /app
# warmup cache and other stuff

And my Caddyfile looks like this now after Symfony’s configuration suggestions:

Caddyfile
localhost

root * /var/www/pub/public
encode zstd gzip
log

php_fastcgi php:9000 {
        root /app/public
}

@phpFile {
        path *.php*
}

error @phpFile "Not found" 404
file_server

It’s the rather unsatisfying answer to, “I have some code. I need to get the code into the Docker container so I can then generate cache/build/transform the files somehow. I need the resulting files to then be served by the webserver, which is in a different container.”

So, what I did was create a Volume for my src directory mounted in both my web (Caddy) and php containers at /var/www/pub and /app respectively.

Because Volumes are only mounted after the container is built, I copy the files from src and transform them while building the php container. Well, in the future, I will (probably php bin/console cache:warmup). For now, it just installs dependencies.

I’m fully willing to accept that I’m doing something dumb here. I just don’t know what the not-dumb thing to do is.

Why not just match the volume in Caddy with what’s in your PHP container, or vice-versa? Just use the same path in both places, and you won’t need to override root.

Alternatively, since you’re using Symfony now, you could use https://frankenphp.dev/ which is a custom distribution of Caddy that has PHP built-in (single container, no php-fpm).

2 Likes

:person_facepalming:

That would be a much better solution, true!

I’ll have a look at FrankenPHP too!

1 Like