By switching to Cloudflared, I was able to remove the firewall rules I originally had that allowed external traffic from Cloudflare’s network to communicate directly with my cluster.
This not only simplifies the overall design but also adds another layer of security to the setup.
As with any technology, things are constantly evolving and changing. I mention this because before I even finished documenting what I had done and how I set it up, Cloudflared came along and turned out to be a much better fit for my environment.
I finally made the call — I’m keeping Traefik in my setup, but I’ve added Cloudflared into the mix.
This gives me the best of both worlds: Traefik handles all my in-cluster routing and middleware, while Cloudflared securely bridges everything through Cloudflare’s network.
Now, all external traffic flows like this:
Cloudflare → Cloudflared → Traefik → Services (like WordPress)
So far, resource utilization has remained about the same, and I haven’t noticed any difference in response times.
The setup feels just as fast and responsive as before, but it’s far more secure and easier to manage.
How It’s Set Up
Cloudflared runs in its own Kubernetes namespace and Deployment.
Instead of replacing Traefik, I just pointed the Cloudflared routes to my internal Traefik endpoint:
https://traefik.kube-system.svc.cluster.local:443
Each route uses an originServerName matching my hostname — for example, www.thedougie.com.
When requests hit Traefik, it recognizes that hostname and forwards them to the right service.
That means www.thedougie.com hits WordPress, mealie.thedougie.com hits Mealie, and so on — just like before.
The Playbook
Here’s the cleaned-up Ansible playbook that handles Cloudflared.
Everything wrapped in {{ }} should live in your Ansible vars file, ideally encrypted with ansible-vault since you’ll be storing your Cloudflare token there. Please note this is not the same as the api key used. this is a token that is generated when you setup zero trust.
# cloudflared-deploy.yml
- name: Deploy Cloudflared (remote-managed) via Helm using provided token
hosts: master
gather_facts: false
vars:
namespace: cloudflared
release: cloudflared
chart: cloudflare/cloudflare-tunnel
tasks:
- name: Ensure namespace exists
kubernetes.core.k8s:
kubeconfig: /etc/rancher/k3s/k3s.yaml
api_version: v1
kind: Namespace
name: "{{ namespace }}"
state: present
- name: Deploy Cloudflared using Deployment manifest and token
kubernetes.core.k8s:
kubeconfig: /etc/rancher/k3s/k3s.yaml
state: present
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
namespace: "{{ namespace }}"
spec:
replicas: 3
selector:
matchLabels:
app: cloudflared
template:
metadata:
labels:
app: cloudflared
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
args:
- tunnel
- --no-autoupdate
- run
- --token
- "{{ cloudflaredtoken }}"
Example Vars File
Here’s what your vars/cloudflared.yml might look like:
cloudflaredtoken: <your-cloudflare-token>
Then encrypt it:
ansible-vault encrypt vars/cloudflared.yml
And run the playbook:
ansible-playbook cloudflared-deploy.yml --ask-vault-pass
Anything in {{ }} comes from your vars file.
If you’re using multiple clusters, remember you can always set {{ kube_context }} in your vars as well — though that’s optional if you only manage one cluster in your kubeconfig.
Verifying Everything
You can check the deployment with:
kubectl get pods -n cloudflared
To see logs:
kubectl logs -n cloudflared deployment/cloudflared
To confirm Traefik’s endpoint:
kubectl get svc -n kube-system traefik
And to test routing internally:
curl -vk https://traefik.kube-system.svc.cluster.local -H "Host: www.thedougie.com"
If you see your app respond, the connection is good.
Wrapping Up
Running Cloudflared inside the cluster with Traefik has been a solid move.
It adds another layer of security, removes the need for open firewall ports, and keeps my existing ingress logic intact.
It also gives me room to grow — whether I eventually migrate to a full Cloudflared standalone setup or keep this hybrid design.
As with everything in the homelab, nothing’s ever truly “done.” Things evolve, better options appear, and sometimes you pivot mid-way. That’s half the fun.