Privacy

Cloudflare Tunnel with Docker: What It Costs You

Cloudflare Tunnel with Docker: the compose file that works, why no port forwarding is needed, and the two trade-offs the free guides leave out.

Cloudflare Tunnel with Docker — outbound-only connections, compose setup, and the trade-offs

⚡ The short version

Run cloudflared as one more service in your compose file with a tunnel token, and it dials out to Cloudflare on port 7844 — no port forwarding, no public IP, nothing listening on your router. The two costs the free guides skip: Cloudflare terminates TLS at its edge, so it holds the plaintext of every request; and on Free, Pro and Business plans its terms restrict serving video and large files through a public hostname, which is exactly what a media server does.

There are three ways to reach a self-hosted service from outside your house, and most guides pick one and stop. A reverse proxy with a forwarded port needs a port you can forward. A mesh VPN needs software on every device that connects. Cloudflare Tunnel with Docker is the third, and it is the answer when you have neither a public IP nor a willingness to open anything.

It is also the one sold hardest and explained least. The setup genuinely is a few lines of YAML. What follows is that, plus the parts that only turn up later.

Why Does Cloudflare Tunnel Need No Port Forwarding?

Because nothing on your side listens. The cloudflared daemon opens the connection outward, and once it is up traffic runs in both directions over it — Cloudflare’s tunnel documentation calls this the outbound-only connection model and notes that most firewalls allow outbound traffic by default, so you can block all inbound traffic and the tunnel is unaffected (checked 7 September 2026).

The destination is port 7844, and that is the one thing your firewall has to permit outbound. Opening port 443 outbound is optional and buys software update checks and Access token validation; Cloudflare’s configuration reference says the tunnel runs correctly without it.

One process is not one connection either. Each cloudflared instance establishes four outbound connections to at least two distinct Cloudflare data centres, so one data centre going offline does not take your service with it (Cloudflare Tunnel configuration documentation, checked 7 September 2026).

This is also what makes the tunnel the answer for CGNAT. If your ISP hands you a shared address rather than your own, there is no port to forward at all — the router you can log into is not the one the internet sees. An outbound connection does not care.

What Goes in the Compose File?

Two things: the cloudflared service, and a token in an environment variable. Cloudflare’s own Docker instruction is a single docker run:

docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token <TUNNEL_TOKEN>

As a compose service, next to the app you want to publish:

services:
  cloudflared:
    image: cloudflare/cloudflared:2026.8.3
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}

  vaultwarden:
    image: vaultwarden/server:latest
    restart: unless-stopped
    volumes:
      - vw-data:/data

volumes:
  vw-data:

2026.8.3 is the current stable cloudflared release tag, read with git ls-remote --tags against the project’s repository on 7 September 2026. Pin it rather than using latest, and keep --no-autoupdate, which Cloudflare’s own command includes: a container that updates itself is a container whose version you cannot state.

The token goes in a .env file beside the compose file, never in the YAML. Cloudflare’s tunnel-token documentation is blunt about why — “anyone with the token can run the tunnel” — and advises rotating tokens regularly, with an API call to force-disconnect existing connections if one leaks (checked 7 September 2026).

Notice what is missing. Vaultwarden has no ports: section. Its image exposes port 80 inside the container (EXPOSE 80 in the project’s Dockerfile template, checked 7 September 2026) and that is all cloudflared needs. Nothing is published to the host, so nothing on your LAN reaches it either — which matters more than it sounds, because Docker’s published ports punch straight through a UFW rule that looks like it should stop them, as the server hardening guide explains.

In the dashboard you then add a published application: a hostname on a domain you have on Cloudflare, and a service URL. Cloudflare creates the DNS record itself, a CNAME to <UUID>.cfargotunnel.com, which only proxies traffic for records in the same account — so a leaked tunnel UUID is not by itself an exposure.

What the tunnel does not do is decide who may open that URL. Anyone who has the hostname reaches whatever is behind it, which is fine for a blog and not fine for an admin panel with no login of its own. Putting an authentik login in front of it is the usual answer, and costs three more containers.

How Does cloudflared Reach a Container by Name?

By service name, on the network Compose already made. The service URL for the stack above is http://vaultwarden:80, not http://localhost:80. Compose creates a network called <project-name>_default, attaches every service to it, and registers each service name with an internal DNS server, so containers reach each other by name with no IP addresses and no manual configuration (Docker Compose networking documentation, checked 7 September 2026).

localhost inside the cloudflared container means the cloudflared container. That single confusion accounts for most of the “tunnel is healthy but I get an error” threads.

Two cases need more than the default. If the app lives in a different compose file, create a network with docker network create and reference it from both files with external: true and a name. And if you want the app to have no route out at all, internal: true creates a network with no connection to the host’s interfaces: put the app on that and cloudflared on both it and a normal one, and inbound traffic still arrives while the app cannot dial out.

The internal-network trick breaks more apps than people expect. No outbound route means no update checks, no OAuth callbacks, no push notifications. It is the right shape for something genuinely self-contained and the wrong shape for most things.

One more trap from the same documentation: network_mode: host turns off service-name DNS resolution entirely, so anything you put in host networking is invisible to cloudflared by name. The Docker for self-hosting guide covers the rest of the compose vocabulary this leans on.

Who Can Read Your Traffic?

Cloudflare can, at its edge, and this is the part the enthusiastic guides leave out. Cloudflare’s encryption-mode documentation puts it plainly: your zone’s setting “controls how Cloudflare manages two connections: one between your visitors and Cloudflare, and the other between Cloudflare and your origin server” (checked 7 September 2026). Two connections means Cloudflare is the endpoint of the first, not a pipe the visitor’s session passes through intact.

It could hardly be otherwise. Cloudflare’s tunnel routing documentation says the network “applies CDN caching, WAF, and DDoS protection before forwarding the request to your origin”, and none of that can be done to bytes you cannot read. The features are the reason people want the tunnel; the visibility is the same fact stated from the other side.

✅ Fine to publish this way

  • A blog, a status page, a recipe wiki — anything you would put on the public web anyway
  • Services where WAF and DDoS filtering are worth more than the plaintext is worth hiding

❌ Think first

  • A password vault, where the threat model is specifically about who holds your data
  • Anything you moved off a cloud service precisely so no third party would hold it

That is a judgement, not a verdict. Vaultwarden encrypts each vault item client-side, so what crosses Cloudflare is ciphertext the edge cannot make sense of — a real answer, and one that does not transfer to Nextcloud serving you a document.

Can You Serve Jellyfin Through It?

Not through a public hostname on a Free, Pro or Business plan, and this catches people out because the tunnel works right up until it does not. Cloudflare’s video-delivery policy page carries a note headed “Cloudflare Tunnel” saying public hostname routes proxy traffic through Cloudflare and, on those three plans, that traffic is subject to the terms on that page. The terms say that if your application “appears to be serving videos or a disproportionate amount of large files without using the appropriate paid service”, Cloudflare may redirect your content or take other action (checked 7 September 2026).

That page offers two ways out, and a third is in the tunnel documentation:

Route What it costs Works for a media server?
Serve from an unproxied subdomain Your real IP is exposed again, and a tunnel hostname cannot be unproxied No
Cloudflare Stream, or Stream Delivery on Enterprise A paid product, and re-encoding your library into it Not realistically
A private network route instead of a public hostname Cloudflare’s client software on every device that connects Yes

The third is the honest answer. Cloudflare’s private-networks documentation states directly that the video restriction “does not apply to private network routes” and that they suit large-file transfers, backups and media servers. The catch is in the same paragraph: to reach one, users must connect their device to Cloudflare and enrol in your organisation. Everyone who wants to watch something installs a client. That is a mesh VPN with extra steps — a real option, but not the free public URL people came for.

The advice you will find is out of date, and confidently so. Forum answers still call this "Section 2.8" and describe it as a ban on non-HTML content. Cloudflare removed section 2.8 on 16 May 2023 and moved a narrower restriction into its service-specific terms, where the line is now whether the content is hosted by a Cloudflare service. Do not reason from the old clause; it says something the current terms do not.

A smaller version of the same wall hits far more people. Cloudflare documents a maximum upload size of 100 MB on Free and Pro, 200 MB on Business, and up to 5 GB on Enterprise, with anything larger refused as 413 Request Entity Too Large (checked 7 September 2026). Push a phone video into Immich through a proxied tunnel hostname and it fails, with an error that looks like your app’s fault and is not.

Cloudflare Tunnel, Tailscale, or a Reverse Proxy?

Pick by who needs to reach the service, not by which is most elegant.

Cloudflare Tunnel Tailscale Reverse proxy, open port
Needs a forwarded port No No Yes
Works behind CGNAT Yes Yes No
Visitor installs anything No Yes No
Who reads the plaintext Cloudflare, at its edge Nobody in the middle Nobody in the middle
Video and large files Restricted on Free, Pro, Business Unrestricted Unrestricted

Cloudflare Tunnel when a real URL has to work for someone who will not install software — a shared photo album, a form, a wiki other people read.

Tailscale when everyone who needs access will run a client. Nothing in the middle holds your plaintext and the video restriction does not exist, but enrolment bites the moment you want to show a relative something on their phone.

A reverse proxy with your own certificate when you have a public IP and a router you control. You keep the plaintext and the bandwidth, and you take on the exposure that goes with a port genuinely open to the internet.

Running two of these at once is normal rather than indecisive: a tunnel for the one service other people use, a mesh for everything that is only ever yours. Only the first half needs a domain pointed at Cloudflare.

Test with a quick tunnel first: cloudflared tunnel --url http://localhost:8080 gives you a random trycloudflare.com address with no account at all. Cloudflare documents it as testing-only, with a 200 concurrent request limit and no support for Server-Sent Events, so do not build on it — but it answers "does this reach my machine" in about ten seconds.

Frequently Asked Questions About Cloudflare Tunnel with Docker

Does Cloudflare Tunnel require opening any ports on my router?

No. The cloudflared daemon makes an outbound connection to Cloudflare on port 7844 and traffic then flows both ways over it, so there is no inbound listener to forward a port to. That is why the tunnel works behind CGNAT, behind a router you do not control, and on a connection with no static IP.

Can Cloudflare read the traffic going through my tunnel?

Yes, at its edge. Cloudflare’s encryption-mode documentation describes two separate connections, one from the visitor to Cloudflare and one from Cloudflare to your origin, which makes Cloudflare the endpoint of the visitor’s TLS session rather than a pass-through. It has to be: caching, the WAF and DDoS protection all read the request before it is forwarded.

Can I run Jellyfin or Plex through a Cloudflare Tunnel?

Not through a public hostname on a Free, Pro or Business plan. Cloudflare’s video-delivery policy page says public hostname routes are subject to terms that restrict serving video or a disproportionate amount of large files without the appropriate paid service. The same page says the restriction does not apply to private network routes, which need Cloudflare’s client software on every device that connects.

Why does my upload fail with a 413 error through the tunnel?

Because the request is larger than the maximum upload size for your Cloudflare plan. Cloudflare documents that limit as 100 MB on Free and Pro, 200 MB on Business, and up to 5 GB on Enterprise, and a request over it is refused with 413 Request Entity Too Large. Neither your Docker setup nor your app’s own upload limit is involved.

How do I point cloudflared at another container instead of localhost?

Use the container’s service name and its internal port, so the service URL points at vaultwarden on port 80 rather than at localhost. Compose puts every service in a project on one network and registers each name with an internal DNS server, so cloudflared resolves the name and connects directly. Nothing has to be published to the host for this to work.

Should I use Cloudflare Tunnel or Tailscale?

Tailscale if the only people who need the service are you and a few others who will install software; Cloudflare Tunnel if a real URL has to work for someone who will not. Tailscale costs you enrolling every device. Cloudflare Tunnel costs you Cloudflare terminating TLS, and the terms restricting video and large files.

Product links on this site are plain links. We earn nothing from them — see our disclosure policy.