Integrity Monitoring: Generates alerts if the boot sequence is tampered with.
C. Identity and API Access
-
Dedicated Service Account: Avoid using the default Compute Engine service account. Create a custom service account with the absolute minimum permissions required.
-
Metadata Security: Ensure
disable-legacy-endpoints = true in the instance metadata to prevent Server-Side Request Forgery (SSRF) attacks from extracting GCP credentials from the metadata server.
D. Secure SSH Access (IAP)
Since there is no public IP, standard SSH over the internet is impossible.
-
Solution: Use Identity-Aware Proxy (IAP) TCP Forwarding. IAP validates Google Identity and IAM permissions before tunneling the SSH connection through GCP's internal backbone to the VM.
3. Step-by-Step Implementation
Step 1: Provisioning the Cloudflare Tunnel
- Navigate to the Cloudflare Zero Trust Dashboard -> Networks -> Tunnels.
- Create a new tunnel and select Cloudflared.
- Add a Public Hostname (e.g.,
app.example.com) and point it to the internal service (http://webapp:8080).
- Copy the generated Tunnel Token.
Step 2: Infrastructure Configuration (Docker Compose)
Docker Compose can be used to run both the service and the cloudflared daemon in the same isolated bridge network.
version: '3.8'
services:
webapp:
image: your-company/webapp:latest
restart: always
environment:
- APP_ENV=production
# Listen on all interfaces inside the container, but expose NO ports to the host
- LISTEN_ADDRESS=0.0.0.0
cloudflared:
image: cloudflare/cloudflared:latest
restart: always
# CRITICAL: Prevent zombie processes by running tini as PID 1
init: true
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=your_secret_token_here
depends_on:
- webapp
Note: Notice there is no ports: ["8080:8080"] mapped to the host. The cloudflared container reaches the web app entirely within the internal Docker network via http://webapp:8080.
Step 3: Run the stack
docker-compose up -d
Within seconds, cloudflared will connect to the Cloudflare Edge, and the site will be securely accessible.
4. Diagnostics & Troubleshooting
When diagnosing connectivity issues, the non-standard traffic flow requires a systematic approach.
A. Diagnosing the Edge (Cloudflare)
A 502 Bad Gateway error indicates that Cloudflare Edge cannot reach the cloudflared tunnel, OR cloudflared cannot reach the target container.
# Check the HTTP response from the outside
curl -I https://app.example.com
B. Diagnosing the Host & Services
Before diving into logs, verify the overall health and resource consumption of the host and Docker containers.
# Check container uptime, status, and IDs
sudo docker ps -a
# Check memory and CPU usage (crucial for diagnosing OOM freezes)
sudo docker stats --no-stream
# Look for stray processes outside of Docker
sudo ps aux | grep cloudflared
sudo systemctl status webapp.service
C. Diagnosing the Tunnel (Cloudflared)
Verify that cloudflared is running and successfully connected to the Edge:
sudo docker logs --tail 50 <cloudflared_container_id>
Look for: INF Registered tunnel connection or ERR Unable to reach the origin service.
D. Verifying Internal Docker Connectivity
Verify that the service is actually alive and responding to the tunnel's requests. Simulate the tunnel's behavior by running a temporary curl container inside the same Docker network:
# Replace 'app_default' with the actual docker network name
sudo docker run --rm --network app_default curlimages/curl -s -I -m 5 http://webapp:8080
If this returns 200 OK, the service is healthy, and the issue lies in the Tunnel or Cloudflare configuration.
5. Common Failures & Edge Cases
[!WARNING]
The Zombie Process (Duplicate Connectors)
When updating or restarting containers (docker-compose down && docker-compose up), Docker sends a SIGTERM to cloudflared. Occasionally, the process ignores the signal, and Docker forcefully orphans it. The process remains alive in the host OS's memory, continuing to send keep-alives to Cloudflare.
Symptom: Cloudflare load-balances traffic between the new healthy container and the old "zombie" process. 50% of incoming requests will randomly return a 502 Bad Gateway.
Fix:
- Find the zombie:
sudo ps aux | grep cloudflared
- Kill the duplicate PIDs:
sudo kill -9 <PID>
-
Prevention: Always add
init: true to the cloudflared service in docker-compose.yml. This forces Docker to use a proper init system (Tini) as PID 1, which reliably reaps and kills child processes.
[!CAUTION]
OOM (Out of Memory) Hangs
If the VM lacks sufficient memory (e.g., using an e2-micro with 1GB RAM for a heavy Node.js app), the application may freeze without the container crashing. The status will show Up X minutes, but the application's event loop is blocked.
Symptom: cloudflared cannot proxy requests, Cloudflare times out after 15 seconds, and returns a 502. Running the diagnostic internal curl command will hang indefinitely.
Fix: Increase the VM machine type (e.g., to e2-medium 4GB) or configure swap space.
[!NOTE]
Protocol Mismatch (HTTP vs HTTPS)
While end-to-end HTTPS is the recommended best practice, this guide uses plain HTTP internally for simplicity. If a protocol mismatch occurs, connectivity will fail.
- If the internal service expects HTTPS, but
cloudflared sends HTTP, the connection will be dropped immediately.
- If
cloudflared is configured to send HTTPS, it will fail if the internal service presents an untrusted/self-signed certificate (unless configured to skip TLS verification).
Ensure the protocol configured in the Cloudflare Zero Trust Dashboard perfectly matches what the internal container expects.