One question I get asked a lot by people just getting into DevOps is deceptively simple: “which cloud should I use to run Docker?” The honest answer is that all three major providers — AWS, Azure, and GCP — support Docker extremely well, but each offers a spectrum of deployment options ranging from “raw VM, you manage everything” to “fully managed, don’t even think about servers.” This guide maps out that entire spectrum across all three providers so you can pick the right layer for your actual workload instead of defaulting to whichever VM tutorial you found first.
The Deployment Spectrum
Across every cloud, Docker deployment options fall into roughly four tiers:
- Raw VM with Docker installed — full control, full responsibility
- Managed container-optimized VM images — less patching overhead, still single-host
- Managed container services (serverless containers) — no host management, pay per container
- Managed Kubernetes — orchestration across many hosts, most powerful, most complex
Let’s go through each provider’s version of each tier.
AWS
Tier 1: EC2 with Docker
The most flexible and most manual option. You provision an EC2 instance, install Docker yourself (or via user-data), and manage the daemon directly.
aws ec2 run-instances \
--image-id ami-0e86e20dae9224db8 \
--instance-type t3.small \
--key-name docker-key \
--user-data file://install-docker.sh
Full walkthrough is covered in the companion “Docker Host on AWS EC2” guide in this series.
Tier 2: Amazon ECS (Elastic Container Service)
ECS runs Docker containers as “tasks” without you managing a Docker daemon directly — AWS handles orchestration. It supports two launch types:
- EC2 launch type — you still own the underlying instances, but ECS handles container placement and lifecycle.
- Fargate launch type — fully serverless; you never see a VM at all.
aws ecs create-cluster --cluster-name docker-cluster
Example minimal task definition:
{
"family": "web-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "web",
"image": "nginx:latest",
"portMappings": [{"containerPort": 80, "protocol": "tcp"}]
}
]
}
aws ecs register-task-definition --cli-input-json file://task-def.json
aws ecs run-task \
--cluster docker-cluster \
--task-definition web-task \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-0123abc],assignPublicIp=ENABLED}"
Tier 3: Amazon EKS
Managed Kubernetes control plane; AWS handles the control plane’s availability and patching, you manage (or also delegate, via Fargate profiles) the worker nodes.
eksctl create cluster --name docker-eks --region us-east-1 --nodes 2
Microsoft Azure
Tier 1: Azure VM with Docker
Same pattern as EC2 — provision a VM, install Docker via cloud-init. Covered fully in the “Docker Host on Azure” guide in this series.
az vm create \
--resource-group docker-rg \
--name docker-host-01 \
--image Ubuntu2404 \
--custom-data cloud-init-docker.yml
Tier 2: Azure Container Instances (ACI)
ACI is Azure’s fastest way to run a single container with zero VM management — genuinely a “docker run, but in the cloud” experience.
az container create \
--resource-group docker-rg \
--name aci-demo \
--image nginx:latest \
--cpu 1 --memory 1 \
--ports 80 \
--ip-address Public
Expected output includes a public IP you can hit immediately:
{
"ipAddress": {
"ip": "20.185.44.201",
"ports": [{"port": 80, "protocol": "TCP"}]
},
"provisioningState": "Succeeded"
}
Tier 3: Azure Kubernetes Service (AKS)
az aks create \
--resource-group docker-rg \
--name docker-aks \
--node-count 2 \
--generate-ssh-keys
az aks get-credentials --resource-group docker-rg --name docker-aks
kubectl get nodes
Google Cloud Platform
Tier 1: Compute Engine with Docker (or Container-Optimized OS)
Covered fully in the “Docker Host on GCE” guide in this series — either install Docker on Ubuntu manually or use the cos-stable image family which ships with Docker preinstalled.
gcloud compute instances create docker-host-01 \
--image-family=cos-stable \
--image-project=cos-cloud
Tier 2: Cloud Run
Cloud Run is GCP’s fully serverless container platform — you give it a container image, it handles scaling (including to zero) and HTTPS automatically.
gcloud run deploy web-demo \
--image=nginx:latest \
--port=80 \
--allow-unauthenticated \
--region=us-central1
Expected output:
Service [web-demo] revision [web-demo-00001-xyz] has been deployed
Service URL: https://web-demo-abcd1234-uc.a.run.app
Tier 3: Google Kubernetes Engine (GKE)
gcloud container clusters create docker-gke \
--num-nodes=2 \
--zone=us-central1-a
GKE Autopilot goes a step further, removing node management entirely:
gcloud container clusters create-auto docker-gke-auto --region=us-central1
Side-by-Side Comparison
| Tier | AWS | Azure | GCP |
|---|---|---|---|
| Raw VM | EC2 | Azure VM | Compute Engine |
| Serverless single container | Fargate (via ECS) | Azure Container Instances | Cloud Run |
| Managed Kubernetes | EKS | AKS | GKE |
| Fully hands-off K8s | EKS on Fargate | AKS with Virtual Nodes | GKE Autopilot |
How to Choose
- Learning Docker, or need custom daemon config / privileged containers / GPU passthrough → raw VM (EC2, Azure VM, or Compute Engine).
- Single service, spiky or low traffic, don’t want to manage anything → Fargate, ACI, or Cloud Run.
- Multiple interdependent services, need service discovery, autoscaling, rolling deploys → managed Kubernetes (EKS, AKS, GKE).
- Batch jobs or scheduled tasks → ECS scheduled tasks, Azure Container Apps jobs, or Cloud Run jobs are usually a better fit than a persistent VM.
Networking Considerations Across All Three
Regardless of provider or tier, the same fundamental rule applies: a container listening on a port inside a host or managed service is not automatically reachable from the internet. Each provider requires an explicit allow rule at its own layer — AWS security groups, Azure Network Security Groups, or GCP firewall rules for raw VMs; load balancer and ingress configuration for managed container/Kubernetes services. This trips up almost everyone the first time they move from docker run -p 80:80 on a laptop to a cloud deployment.
Cost Considerations
- VMs bill for uptime regardless of whether containers are actually doing work — cost-efficient only if utilization stays reasonably high.
- Serverless container services (Fargate, ACI, Cloud Run) bill per-second of actual container execution — much cheaper for spiky or low-traffic workloads, more expensive at sustained high utilization.
- Managed Kubernetes typically has a small control-plane fee (AWS/Azure) or none (GCP’s first cluster), plus the cost of worker nodes — most cost-effective at meaningful scale where the orchestration overhead pays for itself in resource utilization.
Security Considerations Across All Three
- Never expose the raw Docker daemon TCP port without TLS, on any provider.
- Use each provider’s IAM system (IAM roles on AWS, Managed Identities on Azure, Service Accounts on GCP) instead of long-lived static credentials wherever containers need to call other cloud APIs.
- Managed services (Fargate, ACI, Cloud Run, EKS/AKS/GKE) all handle host-level OS patching for you — a meaningful security advantage over raw VMs that you’re now responsible for patching yourself.
Registry Considerations
Wherever you run Docker, images have to come from somewhere, and each provider has its own managed container registry that integrates tightly with its compute services via IAM:
- AWS: Amazon Elastic Container Registry (ECR)
aws ecr create-repository --repository-name my-appaws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.comdocker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0 - Azure: Azure Container Registry (ACR)
az acr create --resource-group docker-rg --name myacrregistry --sku Basicaz acr login --name myacrregistrydocker push myacrregistry.azurecr.io/my-app:1.0.0 - GCP: Artifact Registry (the successor to Container Registry)
gcloud artifacts repositories create my-repo --repository-format=docker --location=us-central1gcloud auth configure-docker us-central1-docker.pkg.devdocker push us-central1-docker.pkg.dev/my-docker-project/my-repo/my-app:1.0.0
Using each provider’s own registry (rather than Docker Hub) for production images avoids Docker Hub’s pull rate limits and keeps image pulls inside the cloud provider’s own network, which is both faster and doesn’t traverse the public internet.
Multi-Cloud and Portability Considerations
Because Docker images and the OCI image format they’re built on are provider-agnostic, the actual container content is fully portable between AWS, Azure, and GCP — the same image that runs on ECS Fargate will run unmodified on Cloud Run or AKS. What isn’t portable without extra work is the surrounding infrastructure-as-code: an ECS task definition, an ACI deployment YAML, and a Cloud Run service spec are all different formats describing the same underlying intent. Teams that need genuine multi-cloud portability typically standardize on Kubernetes manifests (which behave close to identically across EKS, AKS, and GKE) or use a higher-level tool like Terraform or Pulumi to abstract the differences.
A Note on Regional and Zonal Availability
All three providers require you to pick a region (and often a zone) when provisioning compute or container services, and container image pulls, load balancer latency, and cross-service networking costs are all affected by that choice. As a rule of thumb: colocate your compute, your container registry, and any managed database in the same region, and choose a region close to your actual users or, for internal tooling, close to your team.
Summary
Every major public cloud can run Docker, and the real decision isn’t “which cloud” so much as “which tier of abstraction” — raw VM, serverless single-container, or full Kubernetes. Raw VMs (EC2, Azure VM, Compute Engine) give you complete daemon-level control at the cost of managing patching and scaling yourself. Serverless container platforms (Fargate, ACI, Cloud Run) trade some of that control for zero host management and per-second billing. Managed Kubernetes (EKS, AKS, GKE) sits at the top of the stack for teams running many interdependent services that need real orchestration. Understanding this spectrum — rather than reaching for a VM by default — is what actually separates a maintainable container platform from one that becomes an operational headache six months in.
References
- Amazon ECS documentation: https://docs.aws.amazon.com/ecs/
- Amazon EKS documentation: https://docs.aws.amazon.com/eks/
- Azure Container Instances documentation: https://learn.microsoft.com/en-us/azure/container-instances/
- Azure Kubernetes Service documentation: https://learn.microsoft.com/en-us/azure/aks/
- Google Cloud Run documentation: https://cloud.google.com/run/docs
- Google Kubernetes Engine documentation: https://cloud.google.com/kubernetes-engine/docs
- Kubernetes official documentation (CNCF): https://kubernetes.io/docs/home/