How to Start a Docker Host on Google GCE: Complete Setup and Configuration Guide

How to Start a Docker Host on Google GCE

Google Compute Engine is, in my experience, the fastest of the big three clouds to get a working Docker host on — largely because Google ships an official Container-Optimized OS image that has Docker preinstalled and hardened out of the box. This guide covers both that fast path and the more customizable route of installing Docker on a plain Ubuntu image, plus how to secure and monitor whichever one you choose.

Prerequisites

  • A Google Cloud project with billing enabled
  • gcloud CLI installed locally, or run via container (see the companion guide on cloud CLIs in Docker)
  • An SSH key pair (gcloud manages this for you automatically on first connect if you don’t have one)

Step 1: Authenticate and Set Your Project

gcloud auth login
gcloud config set project my-docker-project
gcloud config set compute/zone us-central1-a

Step 2: Enable the Compute Engine API

gcloud services enable compute.googleapis.com

Option A: Fast Path with Container-Optimized OS

Container-Optimized OS (COS) is Google’s minimal, locked-down Linux distribution built specifically to run containers securely, with Docker (and containerd) preinstalled.

gcloud compute instances create docker-host-01 \
  --zone=us-central1-a \
  --machine-type=e2-medium \
  --image-family=cos-stable \
  --image-project=cos-cloud \
  --tags=docker-host

Expected output:

NAME             ZONE           MACHINE_TYPE  STATUS
docker-host-01   us-central1-a  e2-medium     RUNNING

SSH in and Docker is already there:

gcloud compute ssh docker-host-01 --zone=us-central1-a

docker --version
docker run hello-world

Expected output:

Docker version 26.1.4, build 5650f9b
Hello from Docker!
This message shows that your installation appears to be working correctly.

Note that COS’s root filesystem is read-only by design — you can’t apt-get install anything on it. If you need extra tooling, run it as a container instead, which is the intended workflow on COS anyway.

Option B: Custom Setup on Ubuntu with Startup Script

If you want full control over the OS (custom packages, specific kernel modules, etc.), use a plain Ubuntu image with a startup script.

cat > startup-docker.sh << 'EOF'
#!/bin/bash
apt-get update
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
usermod -aG docker $(logname)
systemctl enable docker
systemctl start docker
EOF

gcloud compute instances create docker-host-02 \
  --zone=us-central1-a \
  --machine-type=e2-medium \
  --image-family=ubuntu-2404-lts-amd64 \
  --image-project=ubuntu-os-cloud \
  --tags=docker-host \
  --metadata-from-file=startup-script=startup-docker.sh

Give it a minute for the startup script to finish, then verify:

gcloud compute ssh docker-host-02 --zone=us-central1-a --command="docker --version"

Expected output:

Docker version 27.3.1, build ce12230

Step 3: Configure Firewall Rules

GCE blocks all inbound traffic by default except what you explicitly allow via firewall rules tied to network tags (the --tags=docker-host flag above is what lets us target these instances specifically).

gcloud compute firewall-rules create allow-docker-web \
  --allow=tcp:80,tcp:443 \
  --target-tags=docker-host \
  --source-ranges=0.0.0.0/0
gcloud compute firewall-rules create allow-docker-api \
  --allow=tcp:2376 \
  --target-tags=docker-host \
  --source-ranges=YOUR_IP/32

Always scope the Docker API rule (2376) to a specific source IP range — never 0.0.0.0/0 — since an exposed, unauthenticated Docker daemon is equivalent to granting root access on the host.

Step 4: Securing Remote Docker API Access with TLS

Exactly as with any other cloud, exposing the Docker daemon over TCP requires TLS. On the instance:

mkdir -p ~/docker-certs && cd ~/docker-certs
openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=$(hostname)" -new -key server-key.pem -out server.csr
echo subjectAltName = IP:$(curl -s ifconfig.me),IP:127.0.0.1 > extfile.cnf
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
  -CAcreateserial -out server-cert.pem -extfile extfile.cnf

Configure /etc/docker/daemon.json:

{
  "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
  "tls": true,
  "tlsverify": true,
  "tlscacert": "/etc/docker/ca.pem",
  "tlscert": "/etc/docker/server-cert.pem",
  "tlskey": "/etc/docker/server-key.pem"
}
sudo systemctl restart docker

From your laptop:

export DOCKER_HOST=tcp://EXTERNAL_IP:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=~/docker-certs/client
docker ps

Step 5: Attaching Persistent Disk Storage

For workloads that need storage beyond the boot disk:

gcloud compute disks create docker-data-disk \
  --size=100GB \
  --zone=us-central1-a \
  --type=pd-ssd

gcloud compute instances attach-disk docker-host-02 \
  --disk=docker-data-disk \
  --zone=us-central1-a

On the instance:

sudo lsblk
sudo mkfs.ext4 -m 0 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb
sudo mkdir -p /mnt/docker-data
sudo mount /dev/sdb /mnt/docker-data

Point Docker’s data root there by adding "data-root": "/mnt/docker-data/docker" in daemon.json, then restart Docker.

Networking Fundamentals

GCE VMs sit inside a VPC network, and firewall rules there operate similarly to AWS security groups — allow-list based, applied per network tag or service account. Docker’s own internal networking (the docker0 bridge and any user-defined bridge networks you create) is entirely separate from and sits “inside” the GCE VM’s single network interface; GCE firewall rules only ever see the host’s external and internal IPs, never individual container IPs, since NAT/port publishing (-p) is what maps a host port to a container port.

Running a Real Workload

docker run -d --name web-demo -p 80:80 --restart unless-stopped nginx:latest
gcloud compute instances describe docker-host-02 \
  --zone=us-central1-a \
  --format='get(networkInterfaces[0].accessConfigs[0].natIP)'

Visit the returned IP in a browser to confirm Nginx is reachable.

Monitoring

docker stats --no-stream

For host-level and container-level metrics beyond ad-hoc checks, install the Google Cloud Ops Agent, which natively collects Docker container metrics and forwards them to Cloud Monitoring:

curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh
sudo bash add-google-cloud-ops-agent-repo.sh --also-install

Deploying a Multi-Container Stack with Compose

On the Ubuntu-based host (Option B), Docker Compose is the fastest way to bring up a multi-service application:

# docker-compose.yml
services:
  web:
    image: nginx:latest
    ports:
      - "80:80"
    restart: unless-stopped

  api:
    image: myregistry.example.com/api:1.0.0
    environment:
      - DATABASE_URL=postgresql://appuser:apppass@db:5432/appdb
    restart: unless-stopped

  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=appdb
      - POSTGRES_USER=appuser
      - POSTGRES_PASSWORD=apppass
    restart: unless-stopped

volumes:
  db-data:
docker compose up -d

On Container-Optimized OS, docker-compose isn’t preinstalled the traditional way, but you can run the Compose CLI itself as a container, or simply install the Compose plugin binary directly since COS does allow writing to /home and mounting extra tooling there.

High Availability with Managed Instance Groups

A single Compute Engine VM is a single point of failure. For production traffic, wrap your Docker host configuration in an instance template and a Managed Instance Group (MIG), fronted by a GCP HTTP(S) Load Balancer:

gcloud compute instance-templates create docker-host-template \
  --machine-type=e2-medium \
  --image-family=cos-stable \
  --image-project=cos-cloud \
  --tags=docker-host

gcloud compute instance-groups managed create docker-mig \
  --base-instance-name=docker-host \
  --template=docker-host-template \
  --size=2 \
  --zone=us-central1-a

GCP will automatically replace unhealthy instances in the group based on a configurable health check, giving you self-healing infrastructure without any orchestration layer beyond Compute Engine itself.

Understanding GCP’s Networking Layers

As with any cloud, it pays to keep the layers distinct:

  • VPC network and subnet — the private IP space the instance lives in.
  • Firewall rules, scoped by network tags — the layer gcloud compute firewall-rules create operates on; this is GCP’s equivalent of an AWS security group or Azure NSG.
  • Docker’s internal bridge network — invisible to GCP, handles container-to-container communication on the instance itself.
  • Published ports (-p) — the bridge between the instance’s external IP and the container’s internal port.

A container can be perfectly healthy and still be completely unreachable from the internet if any one of these layers isn’t configured — this is by far the most common “why can’t I reach my container” issue on a first GCE deployment.

Best Practices

  1. Prefer Container-Optimized OS for pure Docker-hosting use cases — its read-only root filesystem and automatic updates meaningfully reduce attack surface compared to a general-purpose distro.
  2. Use network tags and scoped firewall rules, never a blanket 0.0.0.0/0 rule for anything beyond port 80/443.
  3. Use a service account with minimal IAM roles attached to the instance rather than embedding long-lived keys.
  4. Snapshot persistent disks regularly (gcloud compute disks snapshot) if container data matters.
  5. Consider GKE Autopilot instead of a raw Compute Engine VM if you’re going to run more than a handful of services — it removes daemon and node management entirely.

Troubleshooting

  • gcloud compute ssh hangs — firewall rule for SSH (port 22) is enabled by default via the default-allow-ssh rule; check it hasn’t been deleted.
  • Startup script didn’t run — check /var/log/syslog on Ubuntu or use gcloud compute instances get-serial-port-output docker-host-02 to see boot logs, including startup-script output.
  • No external IP / can’t reach the instance — confirm you didn’t pass --no-address; by default gcloud compute instances create assigns an ephemeral external IP unless told otherwise.
  • COS instance rejects apt-get — expected behavior; COS’s root filesystem is intentionally read-only. Install tools as containers instead.

Summary

Google Compute Engine offers a genuinely fast path to a Docker host via Container-Optimized OS, where Docker is already installed, hardened, and auto-updating. For workloads needing a custom OS setup, a plain Ubuntu image with a startup script gets you there in a similar number of steps. Either way, GCE firewall rules and network tags are the layer that decides what’s reachable from outside — Docker’s own container networking remains unchanged from any other Linux host once you’re inside the VM.

References

  • Container-Optimized OS documentation: https://cloud.google.com/container-optimized-os/docs
  • Compute Engine documentation: https://cloud.google.com/compute/docs
  • Docker Engine security documentation: https://docs.docker.com/engine/security/protect-access/
  • Google Cloud Ops Agent documentation: https://cloud.google.com/monitoring/agent/ops-agent
Total
0
Shares

Leave a Reply

Previous Post
How to Start a Docker Host on AWS EC2

How to Start a Docker Host on AWS EC2: Complete Deployment and Configuration Guide

Next Post
How to Starting a Docker Host on Microsoft Azure

How to Start a Docker Host on Microsoft Azure: Complete Deployment and Configuration Guide

Related Posts