Skip to content

Conversation

@k3vmcd
Copy link

@k3vmcd k3vmcd commented Nov 29, 2025

This pull request adds two new example configurations for running Pelican Panel behind a Cloudflare Tunnel, with one variant also supporting secure LAN access via HTTPS. Each example includes a complete Docker Compose stack, Caddy server configuration, environment variable templates, and documentation to help users deploy Pelican Panel securely in different network scenarios.

Cloudflare Tunnel + LAN HTTPS Example:

Configuration and documentation:

  • Added the cloudflare-tunnel-lan example with a full docker-compose.yml stack that binds HTTPS on a specified LAN IP for local clients, while keeping Cloudflare Tunnel for remote access.
  • Provided a Caddyfile that serves the app over HTTP for the tunnel and HTTPS (with internal CA) for LAN traffic, including support for higher PHP upload limits.
  • Added a .env.example file with new variables such as LAN_BIND_ADDRESS to help users configure LAN HTTPS access.
  • Included a detailed README.md explaining setup steps, LAN certificate trust, and DNS configuration for split-horizon access.

Cloudflare Tunnel Example (Internet-only):

Configuration and documentation:

  • Added the cloudflare-tunnel example with a minimal Docker Compose stack for serving Pelican Panel exclusively through Cloudflare Tunnel, keeping host ports closed.
  • Provided a Caddyfile tuned for reverse proxy and upload limits, and a .env.example template for required environment variables. [1] [2]
  • Included a concise README.md guiding users through tunnel setup and deployment.

@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

📝 Walkthrough

Walkthrough

Adds two new example directories demonstrating Pelican Panel deployment with Cloudflare Tunnel integration. The first provides a basic HTTP-only setup, while the second adds LAN HTTPS support with internal CA and local binding. Each includes docker-compose configuration, environment templates, Caddyfile, and documentation.

Changes

Cohort / File(s) Summary
Cloudflare Tunnel (basic)
examples/cloudflare-tunnel/.env.example, examples/cloudflare-tunnel/Caddyfile, examples/cloudflare-tunnel/README.md, examples/cloudflare-tunnel/docker-compose.yml
Adds a basic Cloudflare Tunnel example with HTTP server configuration, environment variables for tunnel token, domain, admin email, and Docker subnet. Includes docker-compose setup with panel and cloudflared services, trusted proxy configuration, PHP-FPM integration, and gzip encoding.
Cloudflare Tunnel (LAN HTTPS)
examples/cloudflare-tunnel-lan/.env.example, examples/cloudflare-tunnel-lan/Caddyfile, examples/cloudflare-tunnel-lan/README.md, examples/cloudflare-tunnel-lan/docker-compose.yml
Adds an extended Cloudflare Tunnel example supporting LAN HTTPS access via internal CA. Includes dual HTTP/HTTPS server configuration in Caddyfile with Strict-Transport-Security header, port 443 binding for LAN clients, environment setup for LAN binding address, and comprehensive usage documentation for resolving APP_URL across local and remote networks.

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding Cloudflare tunnel configurations with two example variants (Internet-only and Internet+LAN).
Description check ✅ Passed The description is directly related to the changeset, detailing both example configurations added and explaining their purposes, configurations, and deliverables.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 29, 2025

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
examples/cloudflare-tunnel/.env.example (1)

13-13: Verify variable substitution support in .env files.

Docker Compose's .env file handling may not expand ${PANEL_DOMAIN} when reading this file—substitution typically works in docker-compose.yml, not in the .env source itself. Users may need to set APP_URL to the literal value https://panel.yourdomain.com instead.

🧹 Nitpick comments (3)
examples/cloudflare-tunnel/docker-compose.yml (1)

16-21: Consider if PANEL_DOMAIN should be passed to the panel service.

The .env.example defines PANEL_DOMAIN, but it's not explicitly passed to the panel service's environment variables. If the panel application or scripts need this value, add it to the environment section. If it's only used for constructing APP_URL, this can remain as-is.

If needed, apply this diff:

     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}
examples/cloudflare-tunnel-lan/Caddyfile (1)

8-38: Consider reducing duplication between HTTP and HTTPS blocks.

The :80 and :443 server blocks contain identical root, encode, php_fastcgi, and file_server directives. While this configuration works correctly, you could reduce duplication using Caddy's snippet feature or by consolidating common directives.

Example refactor using snippets:

 {
   admin off
   servers {
     trusted_proxies static {$DOCKER_SUBNET}
   }
 }
 
+(common) {
+  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
+}
+
 :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
+  import common
 }
 
 :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
+  import common
 }
examples/cloudflare-tunnel-lan/docker-compose.yml (1)

20-20: Consider if PANEL_DOMAIN is needed in the environment.

PANEL_DOMAIN is passed to the panel service but may not be used by the application directly if APP_URL is the canonical configuration variable. Verify whether the panel application or any scripts actually reference this variable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd6e3d and 37cf975.

📒 Files selected for processing (8)
  • examples/cloudflare-tunnel-lan/.env.example (1 hunks)
  • examples/cloudflare-tunnel-lan/Caddyfile (1 hunks)
  • examples/cloudflare-tunnel-lan/README.md (1 hunks)
  • examples/cloudflare-tunnel-lan/docker-compose.yml (1 hunks)
  • examples/cloudflare-tunnel/.env.example (1 hunks)
  • examples/cloudflare-tunnel/Caddyfile (1 hunks)
  • examples/cloudflare-tunnel/README.md (1 hunks)
  • examples/cloudflare-tunnel/docker-compose.yml (1 hunks)
🧰 Additional context used
🪛 LanguageTool
examples/cloudflare-tunnel/README.md

[style] ~13-~13: This phrase is redundant. Consider using “outside”.
Context: ...Usage 1. Copy this directory somewhere outside of the repository, then rename `.env.examp...

(OUTSIDE_OF)

examples/cloudflare-tunnel-lan/README.md

[style] ~17-~17: This phrase is redundant. Consider using “outside”.
Context: ...Usage 1. Copy this directory somewhere outside of the repository, rename .env.example t...

(OUTSIDE_OF)

🔇 Additional comments (6)
examples/cloudflare-tunnel/Caddyfile (1)

1-18: LGTM!

The Caddyfile configuration is well-structured for a Cloudflare Tunnel setup:

  • Admin interface appropriately disabled for production
  • Trusted proxies correctly configured for Docker subnet
  • PHP upload limits properly templated
  • HTTP-only setup is correct since HTTPS termination happens at Cloudflare
examples/cloudflare-tunnel/docker-compose.yml (1)

8-9: LGTM! Correct configuration for tunnel-only access.

Ports are appropriately commented out since all traffic flows through the Cloudflare Tunnel. This prevents direct external access to the panel, which is the intended security posture.

examples/cloudflare-tunnel-lan/README.md (2)

11-13: LGTM! Excellent clarification about APP_URL behavior.

The explanation about why APP_URL must remain consistent and how to achieve local access through split-horizon DNS is clear and addresses a common point of confusion. This guidance will prevent authentication and signed URL issues.


21-21: Based on my verification through web searches, I can now provide the rewritten review comment:


The internal CA certificate path is correct.

The path /data/caddy/pki/authorities/local/root.crt is the standard location where Caddy stores its internal Certificate Authority certificate in Docker containers, as confirmed by Caddy documentation. The Pelican panel Docker image uses /data as the Caddy data directory, making this path accurate. The docker compose cp command is correct as written.

examples/cloudflare-tunnel-lan/Caddyfile (1)

24-26: Based on my verification, I have gathered sufficient information to rewrite the review comment.


The on_demand directive is valid and documented for use with tls internal.

The combination of tls internal with on_demand is supported by Caddy and matches the official documentation. The on_demand subdirective allows Caddy to provision certificates for arbitrary hostnames on demand using the internal CA, rather than pre-provisioning them. This is a legitimate configuration pattern.

examples/cloudflare-tunnel-lan/.env.example (1)

13-13: Based on my verification through multiple web searches, I now have sufficient evidence to determine the accuracy of the original review comment.

The original review comment's primary claim is incorrect. Docker Compose documentation and specification clearly confirm that variable interpolation is supported in .env files, including the ability to reference other variables using the ${VAR} syntax.

The APP_URL=https://${PANEL_DOMAIN} syntax in the .env.example file at line 13 will work as expected, provided that PANEL_DOMAIN is defined in the same .env file.

Variable substitution works in both:

  • .env files (for unquoted and double-quoted values)
  • docker-compose.yml files

The review comment's suggestion to use a literal value like https://panel.yourdomain.com is unnecessary and would actually be less flexible than the current parametrized approach.


No issue with variable substitution in .env files.

Docker Compose supports variable interpolation in .env files using the ${PANEL_DOMAIN} syntax. The current line APP_URL=https://${PANEL_DOMAIN} will correctly expand when PANEL_DOMAIN is defined in the same .env file. This is documented behavior in Docker Compose and works across versions.

networks:
- pelican
ports:
- "${LAN_BIND_ADDRESS:-0.0.0.0}:443:443"
Copy link

@coderabbitai coderabbitai bot Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Security: Default binding to 0.0.0.0 exposes HTTPS to all interfaces.

The LAN_BIND_ADDRESS defaults to 0.0.0.0, which binds the HTTPS port to all network interfaces, potentially exposing the panel to the internet if the host is directly connected. Consider requiring users to explicitly set this variable without a default, or default to 127.0.0.1 with clear documentation to change it.

Apply this diff to require explicit configuration:

     ports:
-      - "${LAN_BIND_ADDRESS:-0.0.0.0}:443:443"
+      - "${LAN_BIND_ADDRESS:?LAN_BIND_ADDRESS must be set in .env}:443:443"

This will cause Docker Compose to fail with a clear error message if LAN_BIND_ADDRESS is not set, forcing users to make a conscious choice.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- "${LAN_BIND_ADDRESS:-0.0.0.0}:443:443"
- "${LAN_BIND_ADDRESS:?LAN_BIND_ADDRESS must be set in .env}:443:443"
🤖 Prompt for AI Agents
In examples/cloudflare-tunnel-lan/docker-compose.yml at line 9, the
LAN_BIND_ADDRESS currently defaults to 0.0.0.0 which exposes HTTPS on all
interfaces; change this to require explicit configuration or default to
127.0.0.1: remove the 0.0.0.0 fallback and use a required env var expansion (so
compose fails if unset) or set the default to 127.0.0.1, and update the
README/usage docs to explain how to override LAN_BIND_ADDRESS when binding to
other interfaces.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point. Maintainers, do you have a preference?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find other docker-compose.yml files in the examples directory to check for patterns
fd -e yml -e yaml docker-compose examples/

# Check how other examples handle port bindings
fd -e yml -e yaml docker-compose examples/ --exec grep -A 2 -B 2 "ports:"

Length of output: 361


@k3vmcd Thanks for checking with the maintainers! I looked through the examples and didn't find an established pattern for port bindings with environment variables—this would be the first.

Here are the trade-offs for each approach to help inform the decision:

Option 1: Require explicit config (no default)

- "${LAN_BIND_ADDRESS:?LAN_BIND_ADDRESS must be set in .env}:443:443"
  • ✅ Most secure: forces conscious decision
  • ✅ Clear error message if forgotten
  • ❌ Slightly less convenient for quick testing

Option 2: Default to 127.0.0.1

- "${LAN_BIND_ADDRESS:-127.0.0.1}:443:443"
  • ✅ Secure by default (localhost only)
  • ✅ Still works out-of-box for tunnel-only usage
  • ✅ Users can override for LAN access
  • ❌ Defeats the purpose of the "LAN" variant if localhost-only

Option 3: Keep 0.0.0.0 with strong documentation

  • ✅ Most convenient, works immediately for LAN use case
  • ❌ Security risk if used carelessly
  • Requires prominent warnings in README

Given this is the LAN variant specifically designed for local network access, Option 2 (defaulting to 127.0.0.1) might not make sense. Perhaps Option 1 with a well-documented .env.example entry would balance security and usability?

Happy to adjust based on what the maintainers prefer! 🙂

@k3vmcd
Copy link
Author

k3vmcd commented Nov 29, 2025

I have read the CLA Document and I hereby sign the CLA

@parkervcp
Copy link
Contributor

@k3vmcd A note here.

I have a PR to run the panel in http if you run it "behind a proxy" with a single variable https://github.com/pelican-dev/panel/pull/1917/files#diff-4f5cabe26761257a4d685a6edc7a43e0fe0f78762f50eeb48530f2bd3b3ee7caR45

@lancepioch
Copy link
Member

Thank you very much for writing these up, I think these would be better in their own section on the docs repo and would love to have them there: https://github.com/pelican-dev/docs

@lancepioch lancepioch closed this Dec 28, 2025
@github-actions github-actions bot locked and limited conversation to collaborators Dec 28, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants