diff --git a/examples/cloudflare-tunnel-lan/.env.example b/examples/cloudflare-tunnel-lan/.env.example new file mode 100644 index 0000000000..45d66a38a4 --- /dev/null +++ b/examples/cloudflare-tunnel-lan/.env.example @@ -0,0 +1,22 @@ +# Cloudflare Tunnel Token +# Get this from: Cloudflare Dashboard -> Zero Trust -> Networks -> Tunnels +# Create a tunnel and copy the token from the install command +CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here + +# Public hostname routed through Cloudflare +PANEL_DOMAIN=panel.yourdomain.com + +# Panel Configuration +ADMIN_EMAIL=admin@yourdomain.com + +# APP_URL (used by Pelican for generating links) +APP_URL=https://${PANEL_DOMAIN} + +# LAN binding address (host IP that should accept LAN HTTPS connections) +LAN_BIND_ADDRESS=192.168.1.50 + +# Docker Network Configuration +DOCKER_SUBNET=172.21.0.0/16 + +# PHP upload/post size limit +PHP_UPLOAD_LIMIT=256M diff --git a/examples/cloudflare-tunnel-lan/Caddyfile b/examples/cloudflare-tunnel-lan/Caddyfile new file mode 100644 index 0000000000..15056075fb --- /dev/null +++ b/examples/cloudflare-tunnel-lan/Caddyfile @@ -0,0 +1,38 @@ +{ + admin off + servers { + trusted_proxies static {$DOCKER_SUBNET} + } +} + +:80 { + root * /var/www/html/public + encode gzip + + php_fastcgi 127.0.0.1:9000 { + env PHP_VALUE "upload_max_filesize = {$PHP_UPLOAD_LIMIT} +post_max_size = {$PHP_UPLOAD_LIMIT}" + } + + file_server +} + +:443 { + root * /var/www/html/public + encode gzip + + tls internal { + on_demand + } + + header { + Strict-Transport-Security "max-age=31536000" + } + + php_fastcgi 127.0.0.1:9000 { + env PHP_VALUE "upload_max_filesize = {$PHP_UPLOAD_LIMIT} +post_max_size = {$PHP_UPLOAD_LIMIT}" + } + + file_server +} diff --git a/examples/cloudflare-tunnel-lan/README.md b/examples/cloudflare-tunnel-lan/README.md new file mode 100644 index 0000000000..99341e9ad8 --- /dev/null +++ b/examples/cloudflare-tunnel-lan/README.md @@ -0,0 +1,24 @@ +# Pelican Panel + Cloudflare Tunnel + LAN HTTPS + +This variant extends the base Cloudflare Tunnel example by also exposing the Panel over HTTPS on your LAN IP. Cloudflared keeps the service private to the internet, while LAN devices can connect straight to the host when they are on the same network. + +## What You Get + +- `docker-compose.yml` that adds a bound 443/tcp listener for LAN clients while keeping the Cloudflare tunnel flow untouched +- `Caddyfile` that continues to serve the tunnel on plain HTTP and serves the same application over HTTPS with an internal CA for LAN traffic +- `.env.example` with the additional `LAN_BIND_ADDRESS` helper so you can decide which host interface should accept LAN HTTPS connections + +## Important Notes About APP_URL + +Pelican only serves requests for the canonical `APP_URL`. To keep authentication and signed URLs working, continue to visit the Panel using that hostname, even on the LAN. Achieve the “local IP” requirement by pointing that hostname at the server’s private IP when you are on-site (split-horizon DNS, router override, or a hosts file entry). When you are away, let public DNS resolve the hostname back to Cloudflare so the tunnel handles traffic. + +## Usage + +1. Copy this directory somewhere outside of the repository, rename `.env.example` to `.env`, and populate the variables. +2. Set `PANEL_DOMAIN`, `APP_URL`, and `ADMIN_EMAIL` as in the base example, then set `LAN_BIND_ADDRESS` to the LAN IP you want Docker to bind on (for example `192.168.1.50`). +3. Create your Cloudflare Tunnel token and assign your hostname to `panel:80` exactly like the base example. +4. Start the stack with `docker compose up -d` and watch logs via `docker compose logs -f cloudflared panel`. +5. On first LAN connection, Caddy issues an internal CA certificate. Export it with `docker compose cp pelican-panel:/data/caddy/pki/authorities/local/root.crt ./pelican-panel-root.crt` and trust it on devices so the browser accepts the LAN HTTPS session. +6. Configure your local DNS/hosts so the `APP_URL` hostname resolves to the LAN IP when you are on-site. Leave public DNS pointing at Cloudflare for remote access. + +Once trusted, LAN traffic stays on your network over HTTPS while remote traffic continues to flow through Cloudflare Zero Trust. diff --git a/examples/cloudflare-tunnel-lan/docker-compose.yml b/examples/cloudflare-tunnel-lan/docker-compose.yml new file mode 100644 index 0000000000..8b39403b03 --- /dev/null +++ b/examples/cloudflare-tunnel-lan/docker-compose.yml @@ -0,0 +1,43 @@ +services: + panel: + image: ghcr.io/pelican-dev/panel:latest + container_name: pelican-panel + restart: always + networks: + - pelican + ports: + - "${LAN_BIND_ADDRESS:-0.0.0.0}:443:443" + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - pelican-data:/pelican-data + - pelican-logs:/var/www/html/storage/logs + - ./Caddyfile:/etc/caddy/Caddyfile + environment: + XDG_DATA_HOME: /pelican-data + APP_URL: ${APP_URL} + ADMIN_EMAIL: ${ADMIN_EMAIL} + PANEL_DOMAIN: ${PANEL_DOMAIN} + DOCKER_SUBNET: ${DOCKER_SUBNET:-172.21.0.0/16} + PHP_UPLOAD_LIMIT: ${PHP_UPLOAD_LIMIT:-256M} + + cloudflared: + image: cloudflare/cloudflared:latest + container_name: cloudflare-tunnel + restart: unless-stopped + networks: + - pelican + command: tunnel --no-autoupdate run + environment: + - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} + +volumes: + pelican-data: + pelican-logs: + +networks: + pelican: + name: pelican + ipam: + config: + - subnet: ${DOCKER_SUBNET:-172.21.0.0/16} diff --git a/examples/cloudflare-tunnel/.env.example b/examples/cloudflare-tunnel/.env.example new file mode 100644 index 0000000000..d020e21acf --- /dev/null +++ b/examples/cloudflare-tunnel/.env.example @@ -0,0 +1,19 @@ +# Cloudflare Tunnel Token +# Get this from: Cloudflare Dashboard -> Zero Trust -> Networks -> Tunnels +# Create a tunnel and copy the token from the install command +CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here + +# Public hostname routed through Cloudflare +PANEL_DOMAIN=panel.yourdomain.com + +# Panel Configuration +ADMIN_EMAIL=admin@yourdomain.com + +# APP_URL (used by Pelican for generating links) +APP_URL=https://${PANEL_DOMAIN} + +# Docker Network Configuration +DOCKER_SUBNET=172.21.0.0/16 + +# PHP upload/post size limit +PHP_UPLOAD_LIMIT=256M diff --git a/examples/cloudflare-tunnel/Caddyfile b/examples/cloudflare-tunnel/Caddyfile new file mode 100644 index 0000000000..ff317c224b --- /dev/null +++ b/examples/cloudflare-tunnel/Caddyfile @@ -0,0 +1,18 @@ +{ + admin off + servers { + trusted_proxies static {$DOCKER_SUBNET} + } +} + +:80 { + root * /var/www/html/public + encode gzip + + php_fastcgi 127.0.0.1:9000 { + env PHP_VALUE "upload_max_filesize = {$PHP_UPLOAD_LIMIT} +post_max_size = {$PHP_UPLOAD_LIMIT}" + } + + file_server +} diff --git a/examples/cloudflare-tunnel/README.md b/examples/cloudflare-tunnel/README.md new file mode 100644 index 0000000000..919e5d66c1 --- /dev/null +++ b/examples/cloudflare-tunnel/README.md @@ -0,0 +1,20 @@ +# Pelican Panel + Cloudflare Tunnel Example + +This example shows how to run Pelican Panel entirely behind a Cloudflare Tunnel so you can keep every port on the host closed while still serving traffic over HTTPS. + +## What You Get + +- `docker-compose.yml` with Pelican Panel and `cloudflared` on an isolated Docker network +- `Caddyfile` tuned for Pelican Panel behind a reverse proxy, including higher upload limits +- `.env.example` documenting the environment variables that the stack expects + +## Usage + +1. Copy this directory somewhere outside of the repository, then rename `.env.example` to `.env` and fill in the variables. +2. Update `APP_URL` and `ADMIN_EMAIL` in the `.env` file to match your domain and administrator address. +3. Create a Cloudflare Tunnel in Zero Trust, copy the tunnel token, and paste it into `CLOUDFLARE_TUNNEL_TOKEN`. +4. Configure the tunnel to route your hostname (for example `panel.yourdomain.com`) to the internal service `panel:80`. +5. Start the stack with `docker compose up -d`. +6. Watch the logs with `docker compose logs -f cloudflared panel` and proceed through the Pelican installer once the services are healthy. + +Detailed guidance and troubleshooting steps in the docs at `docs/panel/advanced/cloudflare-tunnel`. diff --git a/examples/cloudflare-tunnel/docker-compose.yml b/examples/cloudflare-tunnel/docker-compose.yml new file mode 100644 index 0000000000..4ccc85eaa2 --- /dev/null +++ b/examples/cloudflare-tunnel/docker-compose.yml @@ -0,0 +1,42 @@ +services: + panel: + image: ghcr.io/pelican-dev/panel:latest + container_name: pelican-panel + restart: always + networks: + - pelican + #ports: + # Port 80 not exposed - only accessed via Cloudflare Tunnel internally by cloudflared container + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - pelican-data:/pelican-data + - pelican-logs:/var/www/html/storage/logs + - ./Caddyfile:/etc/caddy/Caddyfile + environment: + XDG_DATA_HOME: /pelican-data + APP_URL: ${APP_URL} + ADMIN_EMAIL: ${ADMIN_EMAIL} + DOCKER_SUBNET: ${DOCKER_SUBNET:-172.21.0.0/16} + PHP_UPLOAD_LIMIT: ${PHP_UPLOAD_LIMIT:-256M} + + cloudflared: + image: cloudflare/cloudflared:latest + container_name: cloudflare-tunnel + restart: unless-stopped + networks: + - pelican + command: tunnel --no-autoupdate run + environment: + - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} + +volumes: + pelican-data: + pelican-logs: + +networks: + pelican: + name: pelican + ipam: + config: + - subnet: ${DOCKER_SUBNET:-172.21.0.0/16}