Skip to content

Run Mesh in Docker / Kubernetes

Last updated View as MarkdownAgent setup

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.

Supported architectures

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

Prerequisites

Before starting the container, create a Mesh node and copy its token.

  1. In the Cloudflare dashboard, go to Networking > Mesh.

    Go to Mesh ↗
  2. Select Add a node.

  3. Enter a name for your node (for example, k8s-gateway or docker-agent).

  4. Select Create node.

  5. 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.

Deploy with Docker Compose

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 -d

Verify the node is connected:

docker exec cloudflare-mesh warp-cli status

Deploy with Docker CLI

For 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:latest

Deploy on Kubernetes

This 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).

1. Create the token Secret

kubectl create secret generic cloudflare-mesh \
  --from-literal=MESH_NODE_TOKEN="$MESH_NODE_TOKEN"

2. Apply the manifest

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: 1Gi

3. Verify the node

kubectl apply -f cloudflare-mesh.yaml
kubectl rollout status statefulset/cloudflare-mesh
kubectl exec cloudflare-mesh-0 -- warp-cli status

The PersistentVolumeClaim preserves the Mesh registration across Pod restarts.

Kubernetes sidecar

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.

1. Create the token Secret

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"

2. Apply the manifest

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: http

3. Verify the sidecar

kubectl apply -f cloudflare-mesh-sidecar.yaml
kubectl rollout status statefulset/cloudflare-mesh-sidecar
kubectl exec cloudflare-mesh-sidecar-0 -c mesh -- warp-cli status

Runtime configuration

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

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.

High availability on Kubernetes

For high availability with CIDR routes:

  1. Use the same Mesh node token across multiple replicas.
  2. 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.

Hostname routes

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.

Site-to-site networking

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.

Troubleshooting

Node registers as a regular Cloudflare One Client device

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.

warp-cli status remains Connecting

Check the token, device profile, Gateway proxy, Split Tunnel configuration, outbound firewall connectivity, and container logs:

docker logs cloudflare-mesh

A Kubernetes Service cannot be resolved

Confirm 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.

A hostname request arrives but no response returns

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.

Check node status

docker exec -it cloudflare-mesh warp-cli status
kubectl exec cloudflare-mesh-0 -- warp-cli status

Next steps

Was this helpful?