Caddy PHP portainer setup
Caddy PHP portainer setup
Drop your photos or PDF files
Anonymized by DuckDuckGo. Zero data retention for this chat. No AI training. Learn more
I want to install Caddy PHP to portainer on port 8080. I want to serve web pages from /var/www/html (the same ones serve by Apache PHP (port 80) but on port 8080). I'm using MX25 Linux. Cox Cable blocks Port 80. I need a SSL crtificate. Portainer is on port 9000.
**GPT-5.6 Luna**
Use a Docker Compose stack in Portainer with:
- Caddy listening on host port
8080 - PHP-FPM for PHP processing
- Your existing files mounted from
/var/www/html - DNS-based certificate issuance, since Cox blocks port 80
- Portainer left on port `9000
You need a domain name such as www.example.com pointing to your public IP. A certificate cannot normally be issued for an IP address.
1. Create a Cloudflare API token
The example below uses Cloudflare for DNS validation.
Create a Cloudflare API token with:
Zone:DNS:Edit- Restricted to the zone containing your domain
You can use another DNS provider, but the Caddy image and configuration will be different.
2. Deploy this stack in Portainer
In Portainer, go to Stacks → Add stack, then use:
services:
caddy:
build:
context: ./caddy
container_name: caddy-php
restart: unless-stopped
ports:
- "8080:80"
# Optional direct HTTPS port. Usually not needed when using https://domain:8080.
# - "8443:443"
environment:
CLOUDFLARE_API_TOKEN: "PASTE_YOUR_CLOUDFLARE_TOKEN_HERE"
volumes:
- /var/www/html:/var/www/html:ro
- caddy_data:/data
- caddy_config:/config
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
depends_on:
- php
php:
image: php:8.3-fpm
container_name: caddy-php-fpm
restart: unless-stopped
volumes:
- /var/www/html:/var/www/html
working_dir: /var/www/html
volumes:
caddy_data:
caddy_config:
Portainer stacks normally cannot build a Dockerfile directly unless the build directory exists on the Docker host. Create the following directory and files first on your MX Linux machine:
sudo mkdir -p /opt/caddy-php/caddy
sudo nano /opt/caddy-php/caddy/Dockerfile
Put this in the Dockerfile:
FROM caddy:2-builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare
FROM caddy:2
COPY /usr/bin/caddy /usr/bin/caddy
Then create the Caddy configuration:
sudo nano /opt/caddy-php/caddy/Caddyfile
Use:
www.example.com {
root * /var/www/html
encode gzip
php_fastcgi php:9000
file_server
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}
Replace www.example.com with your actual domain.
Your stack file should then use this build location:
services:
caddy:
build:
context: /opt/caddy-php/caddy
container_name: caddy-php
restart: unless-stopped
ports:
- "8080:80"
environment:
CLOUDFLARE_API_TOKEN: "PASTE_YOUR_CLOUDFLARE_TOKEN_HERE"
volumes:
- /var/www/html:/var/www/html:ro
- caddy_data:/data
- caddy_config:/config
- /opt/caddy-php/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
depends_on:
- php
php:
image: php:8.3-fpm
container_name: caddy-php-fpm
restart: unless-stopped
volumes:
- /var/www/html:/var/www/html
working_dir: /var/www/html
volumes:
caddy_data:
caddy_config:
3. Point your domain to your home connection
At your DNS provider, create an A record:
www.example.com → your public IP address
If your home IP changes, use Dynamic DNS or update the DNS record automatically.
4. Forward port 8080 in your router
Forward:
WAN TCP 8080 → MX Linux server TCP 8080
You should then access the site at:
https://www.example.com:8080
The browser will use the certificate issued for www.example.com, even though the public HTTPS port is 8080.
5. Confirm that Apache is not interfering
Apache can continue serving the same files on port 80. Caddy only needs host port 8080, so there is no conflict.
Check that port 8080 is free:
sudo ss -tulpn | grep ':8080'
If another service is using it, stop that service or choose another host port, for example:
ports:
- "8081:80"
Then you would browse to:
https://www.example.com:8081
Important PHP considerations
The php:8.3-fpm image includes basic PHP but not common extensions such as MySQL, GD, ZIP, or mbstring. If your existing site needs them, use a custom PHP image.
Example:
FROM php:8.3-fpm
RUN docker-php-ext-install mysqli pdo pdo_mysql
Save it as:
/opt/caddy-php/php/Dockerfile
Then replace the PHP service with:
php:
build:
context: /opt/caddy-php/php
container_name: caddy-php-fpm
restart: unless-stopped
volumes:
- /var/www/html:/var/www/html
working_dir: /var/www/html
The DNS challenge is the key part here: it allows Caddy to obtain and renew the SSL certificate without Cox allowing inbound port 80.
In the caddyfile can I use a DuckDNS.org domain name for www.example.com. Can I get a certificate using port 8080?
**GPT-5.6 Luna**
Yes. You can use a DuckDNS hostname, such as:
myhomeserver.duckdns.org
Use the exact DuckDNS hostname in your Caddyfile:
myhomeserver.duckdns.org {
root * /var/www/html
encode gzip
php_fastcgi php:9000
file_server
tls {
dns duckdns {env.DUCKDNS_TOKEN}
}
}
A certificate can be obtained while serving publicly on port 8080, but only when using the DNS-01 challenge. Port 8080 itself cannot satisfy the normal HTTP certificate challenge.
You will need to build Caddy with the DuckDNS DNS plugin. Change the Dockerfile to:
FROM caddy:2-builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/duckdns
FROM caddy:2
COPY /usr/bin/caddy /usr/bin/caddy
Then add your DuckDNS token to the Caddy service:
environment:
DUCKDNS_TOKEN: "YOUR_DUCKDNS_TOKEN"
The Caddy service should expose port 8080 like this:
ports:
- "8080:80"
Forward TCP port 8080 from your router to the MX Linux machine. Also make sure your DuckDNS hostname points to your current public IP address.
You would access the site at:
https://myhomeserver.duckdns.org:8080
The certificate is issued for myhomeserver.duckdns.org; the nonstandard port does not invalidate it. Browsers will display a valid certificate as long as the hostname in the URL exactly matches the hostname in the certificate.
Do not normally use:
www.myhomeserver.duckdns.org
unless you have specifically configured that hostname and DuckDNS supports the required DNS record. The usual DuckDNS format is:
yourname.duckdns.org
The relevant part of the complete Caddy service is:
caddy:
build:
context: /opt/caddy-php/caddy
container_name: caddy-php
restart: unless-stopped
ports:
- "8080:80"
environment:
DUCKDNS_TOKEN: "YOUR_DUCKDNS_TOKEN"
volumes:
- /var/www/html:/var/www/html:ro
- caddy_data:/data
- caddy_config:/config
- /opt/caddy-php/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
depends_on:
- php
Caddy stores the certificate and renewal information in the caddy_data volume, so do not delete that volume when updating the stack.
Give me a complete Dockerfile using the domain n6xre2.duckdns.org
**GPT-5.6 Luna**
Create a directory such as:
sudo mkdir -p /opt/caddy-php
cd /opt/caddy-php
Create this Dockerfile:
FROM caddy:2-builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/duckdns
FROM caddy:2
COPY /usr/bin/caddy /usr/bin/caddy
COPY Caddyfile /etc/caddy/Caddyfile
Create /opt/caddy-php/Caddyfile:
n6xre2.duckdns.org {
root * /var/www/html
encode gzip
php_fastcgi php:9000
file_server
tls {
dns duckdns {env.DUCKDNS_TOKEN}
}
}
Then use this complete docker-compose.yml in Portainer:
services:
caddy:
build:
context: /opt/caddy-php
container_name: caddy-php
restart: unless-stopped
ports:
- "8080:80"
environment:
DUCKDNS_TOKEN: "REPLACE_WITH_YOUR_DUCKDNS_TOKEN"
volumes:
- /var/www/html:/var/www/html:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- php
php:
image: php:8.3-fpm
container_name: caddy-php-fpm
restart: unless-stopped
volumes:
- /var/www/html:/var/www/html
working_dir: /var/www/html
volumes:
caddy_data:
caddy_config:
Before deploying:
- Set the DuckDNS record
n6xre2.duckdns.orgto your current public IP. - Replace
REPLACE_WITH_YOUR_DUCKDNS_TOKEN. - Forward router TCP port
8080to the MX Linux machine’s port8080. - Deploy or redeploy the Portainer stack.
Open the site at:
https://n6xre2.duckdns.org:8080
Caddy will obtain the certificate using the DuckDNS DNS challenge. Port 80 does not need to be available, and Cox’s port-80 blocking will not prevent certificate issuance.
All chats are private. AI can make mistakes.