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 --from=builder /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 --from=builder /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 --from=builder /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:

  1. Set the DuckDNS record n6xre2.duckdns.org to your current public IP.
  2. Replace REPLACE_WITH_YOUR_DUCKDNS_TOKEN.
  3. Forward router TCP port 8080 to the MX Linux machine’s port 8080.
  4. 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.