Move config nginx to Caddy

1. Caddy version:

caddy:2.6-builder-alpine

2. How I installed, and run Caddy:

The docker compose build work fine from localhost

a. System environment:

Docker achitecture with

  • api php via php-fpm
  • front angular with 2 environments:
    • public: localhost:4201
    • backend: localhost:4202

b. Command:

I just start my docker environment.

docker compose up -d

c. Service/unit/compose file:

docker-compose.yml

services:
  php:
    build:
      context: .
      target: app_php
      args:
        SYMFONY_VERSION: ${SYMFONY_VERSION:-5.4}
        STABILITY: ${STABILITY:-stable}
    restart: unless-stopped
    volumes:
      - php_socket:/var/run/php
    healthcheck:
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 30s
    environment:
      DATABASE_URL: mysql://glsr:glsr@mariadb:3306/glsr?serverVersion=mariadb-10.2.44
    links:
      - mariadb

  client:
    build:
      context: .
      target: app_node
    tty: true
    working_dir: '/glsr/client'
    user: '1000:1000'
    command: yarn run start
    volumes:
      - './:/glsr'

  caddy:
    build:
      context: .
      target: app_caddy
    depends_on:
      - php
      - client
    environment:
      SERVER_NAME: ${SERVER_NAME:-localhost, caddy:80}
    restart: unless-stopped
    volumes:
      - php_socket:/var/run/php
      - caddy_data:/data
      - caddy_config:/config
    ports:
      # HTTP
      - target: 80
        published: ${HTTP_PORT:-80}
        protocol: tcp
      # HTTPS
      - target: 443
        published: ${HTTPS_PORT:-443}
        protocol: tcp
      # HTTP/3
      - target: 443
        published: ${HTTP3_PORT:-443}
        protocol: udp

Dockerfile

#syntax=docker/dockerfile:1.4

# The different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
# https://docs.docker.com/compose/compose-file/#target

# Builder images
FROM composer/composer:2-bin AS composer

FROM mlocati/php-extension-installer:latest AS php_extension_installer

FROM node:14.15 AS app_node

RUN yarn global add @angular/cli@11.0.5

COPY --link  ./client ./client

# Build Caddy with the Mercure and Vulcain modules
FROM caddy:2.6-builder-alpine AS app_caddy_builder

RUN xcaddy build \
	--with github.com/dunglas/mercure \
	--with github.com/dunglas/mercure/caddy \
	--with github.com/dunglas/vulcain \
	--with github.com/dunglas/vulcain/caddy

# Prod image
FROM php:7.4-fpm-alpine AS app_php

# Allow to use development versions of Symfony
ARG STABILITY="stable"
ENV STABILITY ${STABILITY}

# Allow to select Symfony version
ARG SYMFONY_VERSION=""
ENV SYMFONY_VERSION ${SYMFONY_VERSION}

ENV APP_ENV=prod

# Allow to select workdir
ARG WORKDIR='/glsr'
ENV WORKDIR=${WORKDIR}

WORKDIR ${WORKDIR}

# php extensions installer: https://github.com/mlocati/docker-php-extension-installer
COPY --from=php_extension_installer --link /usr/bin/install-php-extensions /usr/local/bin/

# persistent / runtime deps
RUN apk add --no-cache \
		acl \
		fcgi \
		file \
		gettext \
		git \
        libmemcached-dev \
	;


RUN set -eux; \
    install-php-extensions \
    	pdo  \
    	pdo_mysql \
    	memcached \
    	intl \
    	zip \
    	apcu \
		opcache \
    ;

###> recipes ###
###< recipes ###

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --link docker/php/conf.d/app.ini $PHP_INI_DIR/conf.d/
COPY --link docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/

COPY --link docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf
RUN mkdir -p /var/run/php

COPY --link docker/php/docker-healthcheck.sh /usr/local/bin/docker-healthcheck
RUN chmod +x /usr/local/bin/docker-healthcheck

HEALTHCHECK --interval=10s --timeout=3s --retries=3 CMD ["docker-healthcheck"]

COPY --link docker/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
RUN chmod +x /usr/local/bin/docker-entrypoint

ENTRYPOINT ["docker-entrypoint"]
CMD ["php-fpm"]

# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER=1
ENV PATH="${PATH}:/root/.composer/vendor/bin"

COPY --from=composer --link /composer /usr/bin/composer

# prevent the reinstallation of vendors at every changes in the source code
COPY --link ./server/composer.* ./server/symfony.* ./
RUN set -eux; \
    if [ -f composer.json ]; then \
		composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress; \
		composer clear-cache; \
    fi

# copy sources
COPY --link  ./server ./
RUN rm -Rf docker/

RUN set -eux; \
	mkdir -p var/cache var/log; \
    chown -R www-data:www-data var; \
    if [ -f composer.json ]; then \
		composer dump-autoload --classmap-authoritative --no-dev; \
		composer dump-env prod; \
		composer run-script --no-dev post-install-cmd; \
		chmod +x bin/console; sync; \
    fi

# Dev image
FROM app_php AS app_php_dev

# Allow to select workdir
ARG WORKDIR='/glsr'
ENV WORKDIR=${WORKDIR}

ENV APP_ENV=dev XDEBUG_MODE=off
VOLUME ${WORKDIR}/var/

RUN rm "$PHP_INI_DIR/conf.d/app.prod.ini"; \
	mv "$PHP_INI_DIR/php.ini" "$PHP_INI_DIR/php.ini-production"; \
	mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

COPY --link docker/php/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/

RUN set -eux; \
	install-php-extensions xdebug

RUN rm -f .env.local.php

# Caddy image
FROM caddy:2.6-alpine AS app_caddy

# Allow to select workdir
ARG WORKDIR='/glsr'
ENV WORKDIR=${WORKDIR}

WORKDIR ${WORKDIR}

COPY --from=app_caddy_builder --link /usr/bin/caddy /usr/bin/caddy
COPY --from=app_php --link /glsr/public ${WORKDIR}/public
COPY --from=app_node --link ./client ${WORKDIR}/client
COPY --link docker/caddy/Caddyfile /etc/caddy/Caddyfile

d. My complete Caddy config:

{
    # Debug
    {$CADDY_DEBUG}
}

{$SERVER_NAME}

log

route {
    handle_path /backend/* {
        reverse_proxy localhost:4202
    }

    handle /api/* {
        reverse_proxy php_fastcgi unix//var/run/php/php-fpm.sock
    }

    handle {
        reverse_proxy localhost:4201
    }

    encode zstd gzip
    file_server
}

3. The problem I’m having:

I’d trying turn my Nginx config:

server {

    listen 80;
    server_name localhost;

    set $projectroot '/glsr';
    set $clientroot 'glsr/client/dist';

    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    client_max_body_size 32M;

    ## Don't log robots.txt requests.
    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    location /api {
        root $projectroot/server/public;
        rewrite ^/api/(.*)$ /index.php/$1 break;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $projectroot/server/public/$fastcgi_script_name;
        fastcgi_param HTTPS off;
        fastcgi_pass php:9000;
    }

    location /backend {
    		proxy_http_version 1.1;
    		proxy_set_header Upgrade    $http_upgrade;
    		proxy_set_header Connection 'upgrade';
    		proxy_pass http://client:4202/backend;
    		proxy_redirect off;
    }

    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_pass  http://client:4201;
        proxy_redirect off;
    }

    ## For the symfony debug bar _profiler
    location ~ ^/index\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $projectroot/server/public/$fastcgi_script_name;
        fastcgi_param HTTPS off;
    }
}

to Caddy (see up the config)

4. Error messages and/or full log output:

When I try https://localhost on my browser:

{"level":"error","ts":1675704738.674929,"logger":"http.log.error","msg":"dial tcp 0.0.0.0:4201: connect: connection refused","request":{"remote_ip":"172.20.0.1","remote_port":"42644","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Te":["trailers"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Cookie":[],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"User-Agent":["Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0"],"Accept-Language":["fr-FR,en-US;q=0.7,en;q=0.3"],"Accept-Encoding":["gzip, deflate, br"],"Dnt":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"localhost"}},"duration":0.001224846,"status":502,"err_id":"uh9dn5igr","err_trace":"reverseproxy.statusError (reverseproxy.go:1272)"}
{"level":"error","ts":1675704738.6749487,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"172.20.0.1","remote_port":"42644","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"Dnt":["1"],"User-Agent":["Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0"],"Accept-Language":["fr-FR,en-US;q=0.7,en;q=0.3"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Te":["trailers"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Cookie":[],"Upgrade-Insecure-Requests":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"localhost"}},"user_id":"","duration":0.001224846,"size":0,"status":502,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"]}}

5. What I already tried:

I begin with the default symfony config:

route {
    root /api/* /glsr/public
    php_fastcgi unix//var/run/php/php-fpm.sock

    reverse_proxy / localhost:4201
    reverse_proxy /backend/* localhost:4202

    encode zstd gzip
    file_server
}

This work without errors, but only api routes works. Nothings happens on the other urls.

6. Links to relevant resources:

From my search:
Using Caddy as a reverse proxy with multiple subdirectories and rewrites
Use Symfony with docker (and Caddy :innocent:)

Hi, welcome! :wave:

I noticed that your address of the backend address is different; the nginx config uses client not localhost.

Also, reverse_proxy php_fastcgi unix//var/run/php/php-fpm.sock should just be php_fastcgi unix//var/run/php/php-fpm.sock.

Hi @matt :wave:

Thanks for your reply.

Yes, and on my angular.json, I have 0.0.0.0:4202:

...,
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "hmr": false,
            "host": "0.0.0.0",
            "port": 4202,
            "disableHostCheck": true,
            "publicHost": "localhost/backend",
            "browserTarget": "backend:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "backend:build:production"
            }
          }
        },
...

I try with 0.0.0.0:4202 and I have same result…

Now, I try with http://client:4202 and http://client:4201, and my front works fine :grinning:. I don’t understand why… With several trying this before, that does not work…

But now, my call to api with

    handle /api/* {
        php_fastcgi unix//var/run/php/php-fpm.sock
    }

doesn’t work anymore. :unamused:. Angular said me Cannot POST/api/login_check when I try to log in.

like this:

    handle /api/* {
        root * /glsr/public
        php_fastcgi unix//var/run/php/php-fpm.sock
    }

It works :+1:

The complete Caddyfile:

{
    # Debug
    {$CADDY_DEBUG}
}

{$SERVER_NAME}

log

route {
    handle /backend/* {
        reverse_proxy http://client:4202
    }

    handle /api/* {
        root * /glsr/public
        php_fastcgi unix//var/run/php/php-fpm.sock
    }

    handle {
        reverse_proxy http://client:4201
    }

    encode zstd gzip
    file_server
}

Thanks @matt

2 Likes

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