Expose Any Docker Container with Cloudflare Tunnel (No Nginx, No Open Ports)
Replace reverse proxies and open ports with a secure, persistent tunnel

🐳 Docker & DevOps Implementation Guides
Complete Docker guides with optimization techniques, deployment strategies, and automation prompts to streamline your containerization workflow.
I’ve lost count of how many times I’ve had to spin up a quick self-hosted app—whether it’s n8n, a Node API, or a dashboard—and then go through the same ritual: open ports, configure Nginx, point DNS to the server, and generate a Let’s Encrypt certificate. It works, but it’s clunky and full of moving parts.
This week, I finally replaced all of that with Cloudflare Tunnel. No Nginx. No Let’s Encrypt. No open ports. Just a persistent, encrypted connection from your server to Cloudflare’s network, automatically routing your container to a public subdomain.
In this guide, I’ll show you exactly how to do it—using n8n as an example, though it works for any container running on any port.
1. The Traditional Setup (and Why It’s Painful)
Normally, exposing a containerized app to the public internet means:
- Opening ports 80 and 443 on your server
- Setting up Nginx as a reverse proxy
- Managing SSL certificates via Let’s Encrypt
- Manually adding a DNS record in Cloudflare or your registrar
That’s a lot of work for something as simple as “make my app reachable at a subdomain.”
2. What Cloudflare Tunnel Does Differently
Cloudflare Tunnel flips the model.
Instead of exposing ports, your server makes a secure outbound connection to Cloudflare. Cloudflare then handles HTTPS, certificates, and routing for you—essentially becoming your reverse proxy at the edge.
The benefits:
- No public ports
- No Nginx
- Automatic SSL
- Built-in DDoS and access control through Cloudflare
All you need is the Cloudflare account that manages your domain and a small CLI agent (cloudflared
).
3. Prerequisites
Make sure you have:
- A Cloudflare-managed domain (e.g.,
example.com
) - A server (Ubuntu 22.04 or later)
- Docker installed
- Cloudflare Tunnel CLI (
cloudflared
)
Install cloudflared
for Ubuntu 22.04 (Jammy → Bullseye base):
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bullseye main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared -y
4. Run Any Container Locally
For the example, let’s use n8n, which runs on port 5678
:
docker volume create n8n_data
docker run -d \
--name n8n \
-p 5678:5678 \
-e GENERIC_TIMEZONE="CET" \
-e TZ="CET" \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-e N8N_RUNNERS_ENABLED=true \
-e WEBHOOK_URL="https://app.example.com/" \
-v n8n_data:/home/node/.n8n \
docker.n8n.io/n8nio/n8n
You can replace this with any container that listens on a port—Grafana (3000
), a Node app (8080
), etc.
5. Authenticate Cloudflare Tunnel
First, log in to Cloudflare from your server:
cloudflared login
This opens your browser, asks you to pick your domain, and then saves credentials to:
~/.cloudflared/cert.pem
6. Create a Named Tunnel
Now create a tunnel and name it (for example, docker-tunnel
):
cloudflared tunnel create docker-tunnel
You’ll see output like:
Created tunnel docker-tunnel with id c101ebf4-e2d3-49b9-8735-c0e48ebf6a56
This also generates a credentials file, usually at:
~/.cloudflared/<tunnel-id>.json
7. Route a Subdomain to the Tunnel
You can create a public subdomain in Cloudflare automatically:
cloudflared tunnel route dns docker-tunnel app.example.com
Cloudflare adds a CNAME record pointing to the tunnel—no manual DNS setup required.
8. Run the Tunnel and Map It to the Container
Let’s connect the subdomain to your local port:
cloudflared tunnel --url http://localhost:5678 run docker-tunnel
You’ll see Cloudflare establish several secure connections to its edge servers. After a few seconds, your app will be available at:
https://app.example.com
9. Make It Persistent with systemd
To keep the tunnel alive after you close the SSH session (and on reboot), use a config file and a systemd service.
Create Config File
File: ~/.cloudflared/config.yml
tunnel: docker-tunnel
credentials-file: /home/matija/.cloudflared/c101ebf4-e2d3-49b9-8735-c0e48ebf6a56.json
ingress:
- hostname: app.example.com
service: http://localhost:5678
- service: http_status:404
Create a systemd Service
File: /etc/systemd/system/cloudflared-docker.service
[Unit]
Description=Cloudflare Tunnel for Docker App
After=network.target
[Service]
User=matija
ExecStart=/usr/bin/cloudflared --config /home/matija/.cloudflared/config.yml tunnel run docker-tunnel
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared-docker.service
Check it’s active:
sudo systemctl status cloudflared-docker
Now your tunnel reconnects automatically after reboot or crash.
10. Verify and Test
Visit your domain:
https://app.example.com
Check Cloudflare DNS — you’ll see a CNAME like:
app.example.com → <tunnel-id>.cfargotunnel.com
No ports are open on your server. Cloudflare terminates HTTPS and securely forwards traffic to your container.
11. (Optional) Add Cloudflare Access
For production, you can protect your exposed container with Cloudflare Access (Zero Trust). From the Cloudflare dashboard:
- Go to Zero Trust → Access → Applications
- Add your subdomain (e.g.,
app.example.com
) - Require Google, GitHub, or email login before granting access
You now have SSO-level protection in front of any local app—no code changes required.
12. Conclusion
Instead of juggling Nginx configs, DNS records, and SSL renewals, Cloudflare Tunnel gives you a single, elegant workflow:
- Run your Docker container locally
- Connect it securely to Cloudflare
- Let Cloudflare handle HTTPS and DNS automatically
- Keep it persistent with a systemd service
This setup is fast, secure, and production-ready—and it works for any container on any port.
If you’ve been doing the old Nginx + Certbot dance for every new side project, this will feel like cheating (in the best way possible).
Thanks, Matija