The cloudflare/mesh ↗ Docker image packages a Cloudflare Mesh node for Linux containers. It runs the Cloudflare One Client's warp-svc daemon headlessly in a minimal Wolfi ↗-based runtime.
Use the container image to add Mesh nodes to Docker Compose stacks, Kubernetes clusters, and CI/CD pipelines — without installing packages on the host.
The latest tag is a multi-platform manifest. Docker automatically selects the appropriate image for the host architecture.
| Architecture | Tag |
|---|---|
| Multi-arch | latest |
| x86-64 | latest-amd64 |
| ARM64 | latest-arm64 |
Before starting the container, create a Mesh node and copy its token.
-
In the Cloudflare dashboard, go to Networking > Mesh.
Go to Mesh ↗ -
Select Add a node.
-
Enter a name for your node (for example,
k8s-gatewayordocker-agent). -
Select Create node.
-
Copy the token shown in the dashboard. You will pass it to the container as
MESH_NODE_TOKEN.
Create a node via the Cloudflare API:
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/warp_connector" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{"name": "k8s-gateway"}'Then retrieve the token:
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/warp_connector/{node_id}/token" \
-H "Authorization: Bearer {api_token}"The response contains the token string. Pass it to the container as MESH_NODE_TOKEN.
If this is your first Mesh node, the dashboard runs a setup wizard that configures your account for Mesh networking.
Docker Compose is the recommended way to run a Mesh node alongside your application services. Add a cloudflare-mesh service to your compose.yaml:
services:
cloudflare-mesh:
image: cloudflare/mesh:latest
container_name: cloudflare-mesh
cap_add:
- NET_ADMIN
- NET_RAW
devices:
- /dev/net/tun:/dev/net/tun
environment:
MESH_NODE_TOKEN: ${MESH_NODE_TOKEN}
SRCNAT_ENABLED: "true"
sysctls:
net.ipv4.ip_forward: "1"
net.ipv6.conf.all.forwarding: "1"
net.ipv6.conf.default.forwarding: "1"
volumes:
- mesh_data:/var/lib/cloudflare-warp
restart: unless-stopped
volumes:
mesh_data:Start the stack:
MESH_NODE_TOKEN="<YOUR-TOKEN>" docker compose up -dVerify the node is connected:
docker exec cloudflare-mesh warp-cli statusFor a standalone container without Compose:
docker run -d \
--name cloudflare-mesh \
--cap-add NET_ADMIN \
--cap-add NET_RAW \
--device /dev/net/tun \
--sysctl net.ipv4.ip_forward=1 \
--sysctl net.ipv6.conf.all.forwarding=1 \
--sysctl net.ipv6.conf.default.forwarding=1 \
-e MESH_NODE_TOKEN="$MESH_NODE_TOKEN" \
-e SRCNAT_ENABLED=true \
-v mesh_data:/var/lib/cloudflare-warp \
--restart unless-stopped \
cloudflare/mesh:latestThis example creates a one-replica StatefulSet with persistent registration state. It requires a Kubernetes cluster that permits NET_ADMIN, NET_RAW, and /dev/net/tun host access (for example, GKE Standard).
kubectl create secret generic cloudflare-mesh \
--from-literal=MESH_NODE_TOKEN="$MESH_NODE_TOKEN"Save the following as cloudflare-mesh.yaml:
apiVersion: v1
kind: Service
metadata:
name: cloudflare-mesh
spec:
clusterIP: None
selector:
app: cloudflare-mesh
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cloudflare-mesh
spec:
serviceName: cloudflare-mesh
replicas: 1
selector:
matchLabels:
app: cloudflare-mesh
template:
metadata:
labels:
app: cloudflare-mesh
spec:
containers:
- name: mesh
image: cloudflare/mesh:latest
env:
- name: MESH_NODE_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-mesh
key: MESH_NODE_TOKEN
- name: SRCNAT_ENABLED
value: "true"
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
volumeMounts:
- name: warp-data
mountPath: /var/lib/cloudflare-warp
- name: dev-net-tun
mountPath: /dev/net/tun
volumes:
- name: dev-net-tun
hostPath:
path: /dev/net/tun
type: CharDevice
volumeClaimTemplates:
- metadata:
name: warp-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gikubectl apply -f cloudflare-mesh.yaml
kubectl rollout status statefulset/cloudflare-mesh
kubectl exec cloudflare-mesh-0 -- warp-cli statusThe PersistentVolumeClaim preserves the Mesh registration across Pod restarts.
To connect an application container to Mesh, add the Mesh image as a sidecar in the same Pod. Containers in a Pod share the network namespace, so the Mesh sidecar connects the application to Cloudflare without any application changes.
Create a separate Mesh node and Kubernetes Secret for the sidecar:
kubectl create secret generic cloudflare-mesh-sidecar \
--from-literal=MESH_NODE_TOKEN="$MESH_NODE_TOKEN"Save the following as cloudflare-mesh-sidecar.yaml:
apiVersion: v1
kind: Service
metadata:
name: cloudflare-mesh-sidecar-headless
spec:
clusterIP: None
selector:
app: cloudflare-mesh-sidecar
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cloudflare-mesh-sidecar
spec:
serviceName: cloudflare-mesh-sidecar-headless
replicas: 1
selector:
matchLabels:
app: cloudflare-mesh-sidecar
template:
metadata:
labels:
app: cloudflare-mesh-sidecar
spec:
containers:
- name: application
image: busybox:1.37.0
command:
- sh
- -c
- |
echo "Hello from the Kubernetes sidecar example" > /tmp/index.html
httpd -f -p 8080 -h /tmp
ports:
- name: http
containerPort: 8080
- name: mesh
image: cloudflare/mesh:latest
env:
- name: MESH_NODE_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-mesh-sidecar
key: MESH_NODE_TOKEN
- name: SRCNAT_ENABLED
value: "true"
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
volumeMounts:
- name: warp-data
mountPath: /var/lib/cloudflare-warp
- name: dev-net-tun
mountPath: /dev/net/tun
volumes:
- name: dev-net-tun
hostPath:
path: /dev/net/tun
type: CharDevice
volumeClaimTemplates:
- metadata:
name: warp-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: cloudflare-mesh-sidecar
spec:
selector:
app: cloudflare-mesh-sidecar
ports:
- name: http
port: 8080
targetPort: httpkubectl apply -f cloudflare-mesh-sidecar.yaml
kubectl rollout status statefulset/cloudflare-mesh-sidecar
kubectl exec cloudflare-mesh-sidecar-0 -c mesh -- warp-cli status| Parameter | Description |
|---|---|
MESH_NODE_TOKEN |
Required for initial registration. Create the token under Networking > Mesh in the Cloudflare dashboard ↗, or via the API. |
SRCNAT_ENABLED |
Controls source NAT. Defaults to true. Accepts true, false, 1, or 0. |
/var/lib/cloudflare-warp |
Stores registration state. Persist this path with a volume to maintain a stable Mesh identity across container recreation. |
Required capabilities and devices
| Capability / device | Why it is needed |
|---|---|
NET_ADMIN |
Creates and configures the tunnel interface, routing, and nftables rules. |
NET_RAW |
Enables raw-socket operations such as ICMP. Docker normally grants this capability by default, but it is declared explicitly here. |
/dev/net/tun |
Creates the WARP TUN interface. |
| IP-forwarding sysctls | Required when the node forwards traffic for routed subnets. |
Source NAT (masquerading) is enabled by default (SRCNAT_ENABLED=true). When a Mesh node receives traffic from the Cloudflare edge and forwards it to a destination on the local network, it translates the source IP from the Mesh CGNAT address (100.96.x.x) to the node's own local interface IP. This ensures return traffic routes correctly without requiring static routes in your VPC or on-premise network.
Set SRCNAT_ENABLED=false only if the attached networks already have return routes to the Mesh IP range (100.96.0.0/12). For more details on return traffic routing, refer to Routes.
For high availability with CIDR routes:
- Use the same Mesh node token across multiple replicas.
- Give each Pod its own
PersistentVolumeClaim.
Cloudflare operates replicas in active-passive mode. If the active replica goes offline, traffic fails over to a standby automatically. A single replica provides no redundancy.
Containers support hostname routing. To resolve Kubernetes Services through a hostname route, make sure the hostname matches the cluster's actual DNS suffix. The default is cluster.local, producing Service names like service.namespace.svc.cluster.local.
Deploy a separate Mesh node container at each site with a separate node token for each node identity. Each node should advertise its locally reachable subnet as a CIDR route. Configure each site's router or workloads to send traffic for the remote subnet through the local Mesh node.
With SRCNAT_ENABLED=true, destinations see the Mesh node's local address. With source NAT disabled, the attached networks require return routes through their Mesh nodes.
Confirm that the correct Mesh node token is set in MESH_NODE_TOKEN. Existing registration state in the persistent volume takes precedence — remove the volume only when you intentionally want to discard that registration and create a new identity.
Check the token, device profile, Gateway proxy, Split Tunnel configuration, outbound firewall connectivity, and container logs:
docker logs cloudflare-meshConfirm that the hostname route matches the cluster's actual DNS suffix. The usual default is cluster.local, producing Service names such as service.namespace.svc.cluster.local.
Check source NAT and return routing first. Verify SRCNAT_ENABLED is set to true or that your network has return routes to the Mesh IP range.
docker exec -it cloudflare-mesh warp-cli statuskubectl exec cloudflare-mesh-0 -- warp-cli status- Add routes — Make subnets behind the containerized node reachable from any device on your Mesh.
- Enable high availability — Run multiple replicas for production resilience.
- Connect from Workers — Use VPC Network bindings to reach private services from Cloudflare Workers.
- Tips and best practices — Cloud VPC configuration, MTU tuning, and running alongside Cloudflare Tunnel.