Deploying Containers via Cloudflare Tunnel (Zero Trust)

1 - How I Built My Own Server Using Local and Cloud Resources
2 - Deploying containers via Cloudflare Tunnel (Zero Trust)
3 - Reverse proxy on Oracle VPS with Traefik
4 - Encrypted Server Backups to S3 with Duplicati
To securely expose the services running on my local server to the internet without opening any ports, I use Cloudflare Tunnel through Cloudflare Zero Trust. This lets my set-top box (STB) act like a public-facing server, even though it’s behind NAT and ISP restrictions.
Cloudflare Tunnel creates an encrypted connection between my device and Cloudflare’s edge network. I don’t need a static IP, DDNS, or port forwarding — just a tunnel and a free Cloudflare account.
Requirements
Before you proceed with the setup, make sure you have the following:
- Docker is installed on your local server and now how to use it. (Tutorial here)
- A Cloudflare account (the free tier is enough)
- A registered domain
- The domain doesn't have to be purchased from Cloudflare — you can buy it from any registrar (e.g., Namecheap, GoDaddy, etc.)
- However, you must point the domain’s nameservers to Cloudflare to use the DNS and Tunnel features
📌 Note: I won’t go into details about registering a domain or setting up nameservers. These steps are already well-covered online — just search for a guide that fits your registrar.
For domains not registered through Cloudflare, this guide might be helpful.

Create a Tunnel
Step 1. Go to the Cloudflare Dashboard. It should look like this.

Step 2. Navigate to Zero Trust

Step 3. Go to Networks -> Tunnels -> Create a Tunnel

Step 4. Select Cloudflared, give the tunnel a name (it can be anything), then save it.


Step 5. Once the tunnel is created, click on Docker and copy the provided Docker command. Save it somewhere for later use.

Set Up Portainer for Easy Docker Management
At first, I prefer to run Portainer to make the later setup easier.
Step 1. Access your local server via SSH, and navigate to your preferred location for installing Portainer.
Step 2. Create a folder called portainer
, then go inside the folder:
mkdir portainer && cd portainer
Step 3. Create Docker networks that will be used by all containers:
# to communicate with cloudflare
docker network create tunnel-network
# to communicate internally
docker network create local-network
Step 4. Run nano docker-compose.yml
, and fill it with the following:
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
ports:
- 8000:9000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- <YOUR_PATH>/portainer_data/data:/data
restart: unless-stopped
cap_drop:
- ALL
security_opt:
- "no-new-privileges=true"
networks:
- net
networks:
net:
external: true
name: local-network
Replace <YOUR_PATH>
with your preferred location on the server where you want to store Portainer's data.
Step 5. Save it and run:
docker compose up -d
Now your Portainer instance is accessible at http://<your-server-ip>:8000
, create an Admin account when prompted.
Connect Cloudflare Tunnel to Your Server
Step 1. In Portainer, go to your local environment → Stacks → Add Stack


Step 2. Enter a name for your stack
Step 3. In the web editor field, paste the following:
services:
cloudflared:
command: 'tunnel --no-autoupdate run --token <YOUR_TOKEN>'
image: 'cloudflare/cloudflared:latest'
container_name: zero-trust
restart: unless-stopped
extra_hosts: ["host.docker.internal:host-gateway"]
networks:
- net
networks:
net:
name: tunnel-network
external: true
Replace <YOUR_TOKEN>
with the token you copied from Cloudflare Zero Trust after creating your tunnel.
Step 4. Click Deploy the stack.

Now your local server is successfully connected to the Cloudflare Tunnel. You can verify this by going to Cloudflare Zero Trust — the Status column should show HEALTHY.

Publish Your First App to the Internet
Once everything is set up, you can now deploy an app and register it with your tunnel using a subdomain.
Step 1. Deploy the app, use Portainer to deploy the app. In this example, we'll use Nginx:
services:
app:
image: nginx:latest
container_name: nginx
networks:
- net
networks:
net:
name: tunnel-network
external: true
When deploying an app, make sure you know the default port it uses. In the case of Nginx, the default is port 80. Always check the official documentation for the Docker image you're using.
Step 2. Register a Subdomain. Go to Cloudflare Zero Trust, click on the three dots of your tunnel, then select Configure.
Step 3. Navigate to the Public Hostname tab, and click Add a public hostname.

Step 4. Fill in the fields:

- In this example, we'll publish Nginx at
nginx.carens.dev
. - The
URL
field should be in the format<container_name>:<container_port>
.
Since the container is namednginx
and uses the default port80
, you can simply enternginx
.
Step 5. Save the hostname settings. Then, try accessing your app via: https://nginx.carens.dev

If everything is successful, you should see the default Nginx welcome page.
Now you're ready to deploy and expose any containerized app through your Cloudflare Tunnel!
Final Thoughts
With this setup, you've taken your first step toward building a modern, secure, and self-hosted server environment. By combining Docker, Portainer, and Cloudflare Tunnel (Zero Trust), you've created a flexible system where your services can be deployed, managed, and securely accessed from anywhere — without exposing your server directly to the internet.
This approach not only simplifies container management but also enhances security and scalability. Whether you're hosting personal projects or small-scale production services, this foundation will serve you well as you expand your infrastructure.
Stay tuned for the next part — where we’ll cover how to configure an Oracle Cloud VPS, and use Traefik for advanced reverse proxying.
Thanks for following along — and happy self-hosting! 🚀
Member discussion