Expose Any Docker Container with Cloudflare Tunnel (No Nginx, No Open Ports)

Replace reverse proxies and open ports with a secure, persistent tunnel

·Matija Žiberna·
Expose Any Docker Container with Cloudflare Tunnel (No Nginx, No Open Ports)

🐳 Docker & DevOps Implementation Guides

Complete Docker guides with optimization techniques, deployment strategies, and automation prompts to streamline your containerization workflow.

No spam. Unsubscribe anytime.

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

0

Comments

Leave a Comment

Your email will not be published

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in