Docker compose configuration with multiple caddy servers?

1. Output of caddy version:

v2.6.2 h1:wKoFIxpmOJLGl3QXoo6PNbYvGW4xLEgo32GPBEjWL8o=

2. How I run Caddy:

In a docker container through docker compose

a. System environment:

Docker version 20.10.20, build 9fdeb9c

DISTRIB_ID=LinuxMint
DISTRIB_RELEASE=20.3
DISTRIB_CODENAME=una
DISTRIB_DESCRIPTION=“Linux Mint 20.3 Una”

b. Command:

docker compose up --build

c. Service/unit/compose file:

version: "3.4"

services:
  php:
    build:
      context: .
      target: app_php_node
      args:
        SYMFONY_VERSION: ${SYMFONY_VERSION:-}
        STABILITY: ${STABILITY:-stable}
    restart: unless-stopped
    network_mode: "host"
    userns_mode: "host"
    volumes:
      - php_socket:/var/run/php
    healthcheck:
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 30s
    environment:
      # Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
      DATABASE_URL: mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
      # Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
      MERCURE_URL: ${MERCURE_URL:-http://caddy/.well-known/mercure}
      MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure
      MERCURE_JWT_SECRET: ${MERCURE_JWT_SECRET:-!ChangeMe!}

  caddy:
    build:
      context: .
      target: app_caddy
    depends_on:
      - php
    environment:
      SERVER_NAME: ${SERVER_NAME:-localhost, caddy:80}
      MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_JWT_SECRET:-!ChangeMe!}
      MERCURE_SUBSCRIBER_JWT_KEY: ${MERCURE_JWT_SECRET:-!ChangeMe!}
    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

# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###

volumes:
  php_socket:
  caddy_data:
  caddy_config:
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###
# Build Caddy with the Mercure and Vulcain modules
FROM caddy:${CADDY_VERSION}.6.2-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

# Caddy image
FROM caddy:${CADDY_VERSION} AS app_caddy

WORKDIR /srv/app

COPY --from=app_caddy_builder /usr/bin/caddy /usr/bin/caddy
COPY --from=app_php_node /srv/app/public public/
COPY docker/caddy/Caddyfile /etc/caddy/Caddyfile

d. My complete Caddy config:

{
    # Debug
    {$DEBUG}
}

{$SERVER_NAME}

log

route {
    root * /srv/app/public
    mercure {
        # Transport to use (default to Bolt)
        transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
        # Publisher JWT key
        publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
        # Subscriber JWT key
        subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
        # Allow anonymous subscribers (double-check that it's what you want)
        anonymous
        # Enable the subscription API (double-check that it's what you want)
        subscriptions
        # Extra directives
        {$MERCURE_EXTRA_DIRECTIVES}
    }
    vulcain
    push
    php_fastcgi unix//var/run/php/php-fpm.sock
    encode zstd gzip
    file_server
}

3. The problem I’m having:

I’m trying to setup a system where multiple caddy instances can co-exist together.
The basic idea is that each branch we create will deploy it’s own docker containers containing it’s own caddy/php-fpm instance.

Say for example someone is working on a redesign, and someone else is working an a feature we want those branches to be accessible at redesign.website.dev and feature.website.dev.

So everytime a branch is deployed docker compose up will be run.

My current problem is that because the first instance already exposes port 80 and 443, the second instance cannot deploy because the ports are already in use. Is there a way to not expose the ports directly but instead using the correct hostname would redirect to the correct service?

4. Error messages and/or full log output:

docker compose up 
[+] Running 1/0
 ⠿ Container docker-compose-integration-php-1  Running                           0.0s
Attaching to docker-compose-integration-caddy-1, docker-compose-integration-php-1
Error response from daemon: driver failed programming external connectivity on endpoint docker-compose-integration-caddy-1 (fb368fb8552d8483a3c12fe8cec9f03f358e1bc9fd14d2c38897705804a1a72f): Bind for 0.0.0.0:443 failed: port is already allocated

5. What I already tried:

I’ve tried putting traefik in front of my applications but once the ports are not exposed anymore I cannot access the application anymore.

I’ve tried disabling https completely and going to the <docker_ip>:80 but to no avail.
I’ve tried using the label system to expose the port/hostname that way but it didn’t seem to have much effect or I missed something

6. Links to relevant resources:

You can use Caddy in the same way, with the GitHub - lucaslorentz/caddy-docker-proxy: Caddy as a reverse proxy for Docker plugin.

Essentially the idea is you’d have one “primary” Caddy instance which binds to 80/443 on the host machine, and proxies to your other ones by domain name.

Your other Caddy instances need to join the same Docker network as the primary so that the CDP plugin can see them via Docker’s API, and those should have Docker labels attached which are the reverse_proxy config to reach them.

You should serve your apps over HTTP (not HTTPS) so that the primary Caddy instance manages certificates and can proxy over HTTP to your apps.

Make sure to config trusted_proxies in php_fastcgi so that X-Forwarded-* headers are properly passed through to your PHP app.

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