This check flags GCP firewall rules that expose the Docker daemon (ports 2375/2376) to the public internet. An open Docker socket is effectively remote root on the host, so close the ports to specific internal sources or delete the rule entirely.
Exposing the Docker daemon to the internet is one of those misconfigurations that looks harmless in a firewall list but hands an attacker complete control of your host. The vpc_opendocker check looks at your GCP VPC firewall rules and raises a finding when a rule permits inbound traffic on TCP ports 2375 or 2376 from 0.0.0.0/0 or any other public range.
If you run Docker on Compute Engine instances and someone opened the daemon API for "remote debugging" or a CI integration, this is the check that catches it before an attacker does.
What this check detects
The Docker daemon can listen on a TCP socket so that remote clients can manage containers over the network. Two ports are involved:
- 2375 — the unencrypted, unauthenticated Docker API. Anyone who can reach this port has full control of the daemon.
- 2376 — the TLS-secured Docker API. Safer in principle, but only if mutual TLS is actually configured and enforced. Exposed to the public it is still a large attack surface.
The check inspects each firewall rule in your VPCs and flags any rule that:
- Has a direction of
INGRESSand an action ofALLOW - Permits TCP port 2375 or 2376 (directly, or via a range that includes them)
- Has a source range that is public, such as
0.0.0.0/0
Note: The Docker daemon does not listen on a TCP port by default. It uses a Unix socket at /var/run/docker.sock. Someone has to explicitly configure -H tcp://0.0.0.0:2375 or set hosts in daemon.json for this risk to exist. The firewall rule is one half of the problem.
Why it matters
The Docker daemon runs as root, and the API has no concept of granular permissions. If you can talk to it, you can do anything the daemon can do. That includes mounting the host filesystem into a container and reading or writing any file on the machine.
Here is the attack in three commands. An attacker who reaches an exposed port 2375 runs:
export DOCKER_HOST=tcp://VICTIM_IP:2375
# Launch a container that mounts the host root filesystem
docker run -v /:/host -it alpine chroot /host sh
# Now they have a root shell on the underlying VM
cat /host/etc/shadow
From there the attacker has full control of the instance: they can pull the GCE metadata server credentials, pivot into your project, deploy cryptominers, or exfiltrate data. Botnets actively scan the entire IPv4 range for open Docker ports, so this is not a targeted-attack-only problem. An exposed daemon is usually compromised within hours.
Danger: Port 2375 has no authentication whatsoever. If it is reachable from the internet, treat the host as already breached. Rotate any credentials that were present on that instance, including service account keys and tokens cached from the metadata server.
The metadata server angle is what turns a single-host compromise into a project-wide one. By default a Compute Engine instance can reach http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token and grab an access token for its attached service account. If that account has broad IAM roles, the blast radius is your whole GCP project.
How to fix it
There are two layers to address: the firewall rule and the daemon configuration. Fix the firewall first because it removes the exposure immediately.
1. Find the offending rule
gcloud compute firewall-rules list \
--format="table(name, sourceRanges.list(), allowed[].map().firewall_rule().list())" \
--filter="allowed.ports:(2375 OR 2376)"
2. Inspect it before changing anything
gcloud compute firewall-rules describe RULE_NAME --format=json
Check what the rule actually targets. If it uses targetTags or targetServiceAccounts, you want to know which instances rely on it.
Warning: If a CI pipeline or an orchestration tool depends on remote Docker access, deleting the rule outright can break deployments. Confirm what uses the daemon before you remove the rule, then plan to migrate those callers to a private path (VPN, internal load balancer, or IAP tunnel).
3. Option A — Restrict the source range
If you genuinely need remote Docker access from a known network, lock the source down to specific internal CIDRs rather than the public internet.
Danger: The command below modifies a live firewall rule. Updating --source-ranges replaces the existing ranges entirely, so include every range you intend to keep. Run it in a maintenance window if instances depend on the current rule.
gcloud compute firewall-rules update RULE_NAME \
--source-ranges=10.0.0.0/8
3. Option B — Delete the rule
If nothing legitimately needs remote daemon access, remove the rule. This is the safest outcome.
gcloud compute firewall-rules delete RULE_NAME
4. Stop the daemon from listening on TCP
Closing the firewall removes external exposure, but you should also stop the daemon from binding the TCP socket unless you have a real reason for it. On the affected instance, edit /etc/docker/daemon.json:
{
"hosts": ["unix:///var/run/docker.sock"]
}
If the daemon was started with a -H tcp:// flag in a systemd drop-in, remove that flag, then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart docker
Tip: If you need remote Docker access for a real workflow, do not open a port at all. Use the SSH transport instead: DOCKER_HOST=ssh://user@host docker ps. It rides over your existing SSH access with no extra firewall holes and no certificate management.
How to prevent it from happening again
Manual cleanup fixes today's finding. Policy and pipeline gates stop the next one.
Block it in Terraform with a policy check
If you provision firewall rules with Terraform, scan plans in CI before they apply. A Checkov custom policy or a built-in rule catches an open Docker port at review time:
checkov -d . --check CKV_GCP_2
You can also write an OPA/Rego guardrail that fails any rule allowing 2375 or 2376 from a public source:
{
"deny": "firewall rule exposes Docker daemon (2375/2376) to a public range"
}
Use GCP Organization Policy
The constraints/compute.vmExternalIpAccess constraint and VPC firewall policies at the org level let you centrally forbid risky ingress patterns. Hierarchical firewall policies are particularly useful here because they apply before project-level rules and cannot be overridden by a single team.
Tip: Pair a deny rule with continuous scanning. Lensix runs vpc_opendocker across your projects on a schedule, so a rule that slips past CI through a console change or a manual gcloud command still gets flagged.
Best practices
- Never expose the Docker daemon to the internet. There is almost no legitimate reason to. If remote control is needed, use SSH transport or an orchestrator like Kubernetes with proper RBAC.
- Default to deny. Start with no public ingress and open only the specific ports you can justify, scoped to the narrowest source range possible.
- Tighten service account scope. Give Compute Engine instances least-privilege service accounts so that a host compromise does not become a project compromise.
- Restrict metadata access. Enable
shieldedInstancefeatures and, where possible, limit which workloads can reach the metadata server to slow down credential theft. - Audit firewall rules regularly. Rules accumulate. Periodically review every
0.0.0.0/0ingress rule and ask whether it still needs to exist. - Prefer named, documented rules. A rule called
allow-docker-debugis a flag for review. An untagged rule on port 2375 with no description is a liability nobody remembers creating.
The fix here is quick, but the lesson is broader: the Docker daemon's network API trades convenience for a root-level attack surface. Keep it on the Unix socket, reach it over SSH when you need to, and let your firewall default to closed.

