1. The problem I’m having:
I use Caddy 2.7 as a reverse proxy in a project with Api Platform on the backend and Nuxt on the frontend. It works fine in dev but in prod I get the following error:
2. Error messages and/or full log output:
Here is my logs:
{
"container_name": "app-php-1",
"image": "app-php",
"label": {
"com.docker.compose.oneoff": "False",
"com.docker.compose.project": "app",
"com.docker.compose.project.config_files": "/app/compose.yaml,/app/compose.prod.yaml",
"com.docker.compose.project.working_dir": "/app",
"com.docker.compose.service": "php",
"com.docker.compose.version": "2.24.5",
"org.opencontainers.image.source": "https://github.com/dunglas/frankenphp",
"org.opencontainers.image.title": "FrankenPHP",
"org.opencontainers.image.url": "https://frankenphp.dev",
"org.opencontainers.image.vendor": "Kévin Dunglas",
"org.opencontainers.image.version": "v1.1.0"
},
"message": {
"duration": 0.000750297,
"err_trace": "reverseproxy.statusError (reverseproxy.go:1267)",
"level": "error",
"logger": "http.log.error.log0",
"msg": "dial tcp 172.29.0.4:3000: connect: connection refused",
"request": {
"client_ip": "123.456.789.135",
"headers": {
"Accept": [
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
],
"Accept-Encoding": [
"gzip, deflate, br"
],
"Accept-Language": [
"en-US,en;q=0.9"
],
"Connection": [
"keep-alive"
],
"Sec-Ch-Ua": [
"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111"
],
"Sec-Fetch-Mode": [
"navigate"
],
"User-Agent": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
]
},
"host": "preprod.mywebsite.fr",
"method": "GET",
"proto": "HTTP/1.1",
"remote_ip": "123.456.789.135",
"remote_port": "11177",
"tls": {
"cipher_suite": 4865,
"proto": "http/1.1",
"resumed": false,
"server_name": "preprod.mywebsite.fr",
"version": 772
},
"uri": "/"
},
"status": 502,
"ts": 1708192143.2819047
},
"source_type": "docker_logs",
"stream": "stderr",
"timestamp": "2024-02-17T17:49:03.281992398Z"
}
3. Caddy version: 2.7
4. How I installed and ran Caddy:
My project is installed here: /app/ with my Caddyfile in /app/api/frackenphp/.
So the backend is in /app/api/
And the frontend in /app/pwa/.
a. System environment:
I use Docker and Docker Compose 2 on Ubuntu 22.04
b. Command:
I launch the application with the following command:
SERVER_NAME=preprod.mywebsite.fr \
APP_SECRET=12345 \
MARIADB_ROOT_PASSWORD=12345 \
CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \
docker compose -f compose.yaml -f compose.prod.yaml up --wait
c. Service/unit/compose file:
My compose.yaml file :
services:
php:
image: ${IMAGES_PREFIX:-}app-php
depends_on:
- database
restart: unless-stopped
environment:
PWA_UPSTREAM: pwa:3000
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
TRUSTED_HOSTS: ^${SERVER_NAME:-example\.com|localhost}|php$$
DATABASE_URL: mysql://${MARIADB_USER:-mywebsite}:${MARIADB_PASSWORD:-12345}@database:3306/${MARIADB_DATABASE:-mywebsite}?serverVersion=mariadb-${MARIADB_VERSION:-10.11.5}&charset=utf8mb4
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
volumes:
- 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
pwa:
image: ${IMAGES_PREFIX:-}app-pwa
My compose.prod.yaml file:
# Production environment override
services:
php:
build:
context: ./api
target: frankenphp_prod
environment:
APP_SECRET: ${APP_SECRET}
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
pwa:
build:
context: ./pwa
target: prod
d. My complete Caddy config:
{
{$CADDY_GLOBAL_OPTIONS}
frankenphp {
{$FRANKENPHP_CONFIG}
}
# https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
order mercure after encode
order vulcain after reverse_proxy
order php_server before file_server
}
{$CADDY_EXTRA_CONFIG}
{$SERVER_NAME:localhost} {
log {
# Redact the authorization query parameter that can be set by Mercure
format filter {
wrap console
fields {
uri query {
replace authorization REDACTED
}
}
}
}
root * /app/public
encode zstd gzip
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
# Add links to the API docs and to the Mercure Hub if not set explicitly (e.g. the PWA)
header ?Link `</docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation", </.well-known/mercure>; rel="mercure"`
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
header ?Permissions-Policy "browsing-topics=()"
# Matches requests for HTML documents, for static files and for Nuxt files,
# except for known API paths and paths with extensions handled by API Platform
@pwa expression `(
header({'Accept': '*text/html*'})
&& !path(
'/media*', '/docs*', '/graphql*', '/bundles*', '/media*', '/contexts*', '/_profiler*', '/_wdt*',
'*.json*', '*.html', '*.csv', '*.yml', '*.yaml', '*.xml'
)
)
|| path('/favicon.ico', '/manifest.json', '/robots.txt', '/_nuxt*', '/sitemap*')`
# Comment the following line if you don't want Nuxt to catch requests for HTML documents.
# In this case, they will be handled by the PHP app.
reverse_proxy @pwa http://{$PWA_UPSTREAM}
php_server
}
Thank you for your help.