Run containers and VMs
Push an image, run containers and start virtual machines on pogi with AWS, Google Cloud and Azure calls.
pogi runs your containers and virtual machines (VMs) as real Docker containers. You'll push an image, run a task, start a VM and run a web container, on each cloud.
Before you start
Start pogi in another terminal:
pogi startLoad the settings that point your tools at pogi:
eval "$(pogi env)"To use az (the Container Instances section), sign it in to pogi too. Any client ID and secret work, and AZURE_CONFIG_DIR keeps your own ~/.azure as it is. Run this in the shell where you run az. The steps and what each line does are in Azure CLI:
eval "$(pogi env)"
export REQUESTS_CA_BUNDLE="$HOME/.pogi/certs/ca.pem" # pogi's CA; `pogi cert` prints its path
export AZURE_CONFIG_DIR="$HOME/.pogi/azure" # keeps your own ~/.azure as it is
az cloud show -n pogi > /dev/null 2>&1 || az cloud register -n pogi --endpoint-resource-manager https://localhost:7644/
az cloud set -n pogi --only-show-errors
az login --service-principal -u 00000000-0000-0000-0000-000000000001 -p secret \
--tenant 00000000-0000-0000-0000-000000000000 --output noneYou need Docker, plus the AWS CLI for the AWS sections, gcloud for the Google Cloud sections, and the Azure CLI (az) and curl for Azure. The first run of each image pulls it. Every section deletes what it made at the end.
AWS: ECR and ECS
You'll push an image to a registry (ECR), run it as a task (ECS), and read what it wrote to its log.
-
Create a repository. pogi runs a real registry at
localhost:4510:aws ecr create-repository --repository-name demo --query repository.repositoryUri --output textOutput localhost:4510/demo -
Log in, then tag and push a small public image.
-qkeepsdockerfrom printing every layer:aws ecr get-login-password | docker login --username AWS --password-stdin localhost:4510 docker pull -q alpine docker tag alpine localhost:4510/demo:v1 docker push -q localhost:4510/demo:v1Output (trimmed) Login Succeeded docker.io/library/alpine:latest localhost:4510/demo:v1 …Docker Desktop may add an
Infoline saying it pushed only the image for your platform. -
List the image:
aws ecr describe-images --repository-name demo --query 'imageDetails[].[imageTags[0],imageSizeInBytes]' --output textOutput v1 4190488 -
Make a cluster, a log group and a task definition. The task runs
echoin the image you pushed and sends its output to the log group:aws ecs create-cluster --cluster-name demo --query cluster.status --output text aws logs create-log-group --log-group-name /ecs/hello aws ecs register-task-definition --family hello --query taskDefinition.taskDefinitionArn --output text \ --container-definitions '[{"name":"hello","image":"localhost:4510/demo:v1","command":["echo","hello from ecs"],"essential":true,"memory":64,"logConfiguration":{"logDriver":"awslogs","options":{"awslogs-group":"/ecs/hello","awslogs-region":"us-east-1","awslogs-stream-prefix":"demo"}}}]'Output ACTIVE arn:aws:ecs:us-east-1:000000000000:task-definition/hello:1 -
Run the task and wait for it to stop:
TASK=$(aws ecs run-task --cluster demo --task-definition hello --query 'tasks[0].taskArn' --output text) aws ecs wait tasks-stopped --cluster demo --tasks "$TASK" aws ecs describe-tasks --cluster demo --tasks "$TASK" --query 'tasks[0].[lastStatus,containers[0].exitCode]' --output textOutput STOPPED 0 -
Read the log:
STREAM=$(aws logs describe-log-streams --log-group-name /ecs/hello --query 'logStreams[0].logStreamName' --output text) aws logs get-log-events --log-group-name /ecs/hello --log-stream-name "$STREAM" --query 'events[].message' --output textOutput hello from ecs -
Clean up:
aws ecs delete-cluster --cluster demo --query cluster.status --output text aws ecs deregister-task-definition --task-definition hello:1 --query taskDefinition.status --output text aws logs delete-log-group --log-group-name /ecs/hello aws ecr delete-repository --repository-name demo --force --query repository.repositoryName --output text docker rmi localhost:4510/demo:v1Output INACTIVE INACTIVE demo Untagged: localhost:4510/demo:v1
AWS: EC2
You'll start an instance, see it running, and end it. pogi runs an instance as a container made from the image that matches its AMI (the machine image ID).
-
Pick an AMI. pogi has a small catalog:
aws ec2 describe-images --query 'Images[].[ImageId,Name]' --output textOutput ami-0a1b2c3d4e5f60001 al2023-ami-2023.6.20250101.0-kernel-6.1-x86_64 ami-0a1b2c3d4e5f60002 ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-20250101 ami-0a1b2c3d4e5f60003 ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20250101 ami-0a1b2c3d4e5f60004 amzn2-ami-kernel-5.10-hvm-2.0.20250101.0-x86_64-gp2 ami-0a1b2c3d4e5f60005 debian-12-amd64-20250101-1980 ami-0a1b2c3d4e5f60006 alpine-3.21.0-x86_64-bios-cloudinit-r0 -
Start an instance from the Alpine image:
INSTANCE=$(aws ec2 run-instances --image-id ami-0a1b2c3d4e5f60006 --instance-type t3.micro \ --query 'Instances[0].InstanceId' --output text) aws ec2 wait instance-running --instance-ids "$INSTANCE" aws ec2 describe-instances --instance-ids "$INSTANCE" \ --query 'Reservations[0].Instances[0].[State.Name,InstanceType,ImageId]' --output textOutput running t3.micro ami-0a1b2c3d4e5f60006 -
Run a command inside it. The container is named
pogi-ec2-<instance id>:docker exec "pogi-ec2-$INSTANCE" cat /etc/alpine-releaseOutput 3.24.3 -
Terminate it:
aws ec2 terminate-instances --instance-ids "$INSTANCE" --query 'TerminatingInstances[0].CurrentState.Name' --output textOutput shutting-down
Google Cloud: Cloud Run
You'll deploy a web container with gcloud run deploy and call it. The container starts on the first request. pogi env points gcloud run at run.localhost, because gcloud puts the region in front of that name (us-central1-run.), and *.localhost names reach your own machine on macOS and most Linux systems. pogi doctor checks yours.
-
Deploy the service:
gcloud run deploy hello --image nginx:alpine --port 80 --region us-central1 --allow-unauthenticatedOutput Enabling APIs on project [pogi]... Deploying container to Cloud Run service [hello] in project [pogi] region [us-central1] Deploying new service... Setting IAM Policy.....done Done. Service [hello] revision [hello-00001-uqj] has been deployed and is serving 100 percent of traffic. Service URL: http://hello.run.localhost:7644 -
List the services and read the address:
gcloud run services list --region us-central1 gcloud run services describe hello --region us-central1 --format "value(status.url)"Output SERVICE REGION URL LAST DEPLOYED BY LAST DEPLOYED AT ✔ hello us-central1 http://hello.run.localhost:7644 2026-10-26T09:04:40Z http://hello.run.localhost:7644 -
Call it. The service answers at
<service>.run.localhost:curl -s http://hello.run.localhost:7644/ | grep -o "<title>.*</title>"Output <title>Welcome to nginx!</title> -
Delete the service. This stops its container:
gcloud run services delete hello --region us-central1 --quietOutput Deleting [hello]... done. Deleted service [hello].
Google Cloud: Compute Engine
You'll start a VM with gcloud and run a command inside it. pogi runs the VM as a container of its boot image.
-
Create the VM:
gcloud compute instances create vm1 --zone us-central1-a --machine-type e2-micro \ --image-family debian-12 --image-project debian-cloudOutput There is no default service account for project pogi. Instance vm1 will not have scopes. Created [https://www.googleapis.com/compute/v1/projects/pogi/zones/us-central1-a/instances/vm1]. NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS vm1 us-central1-a e2-micro 10.128.0.2 34.100.0.3 RUNNING -
Run a command inside it. The container's
pogi.resourcelabel is the instance's numeric ID:ID=$(gcloud compute instances describe vm1 --zone us-central1-a --format "value(id)") docker exec $(docker ps -q --filter label=pogi.service=compute --filter label=pogi.resource=$ID) head -1 /etc/os-releaseOutput PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" -
Delete the VM:
gcloud compute instances delete vm1 --zone us-central1-a --quietOutput Deleted [https://www.googleapis.com/compute/v1/projects/pogi/zones/us-central1-a/instances/vm1].
Azure: Container Instances
You'll run a web container with az container (Azure Resource Manager, the API behind the Azure portal) and open it with curl.
-
Create a resource group and a container group:
az group create -n demo -l eastus -o none az container create -g demo -n hello --image nginx:alpine --ports 80 \ --dns-name-label hello --location eastus --os-type Linux \ --query "{name:name,fqdn:ipAddress.fqdn}" -o tsvOutput hello hello.eastus.azurecontainer.localhost:7644 -
Check that the container is running:
az container show -g demo -n hello --query instanceView.state -o tsvOutput Running -
Call it. The group answers at
<label>.<location>.azurecontainer.localhost:curl -s http://hello.eastus.azurecontainer.localhost:7644/ | grep -o "<title>.*</title>"Output <title>Welcome to nginx!</title> -
Delete the container group and the resource group:
az container delete -g demo -n hello --yes -o none az group delete -n demo --yesIt prints nothing.
Limits
- ECR: one registry host serves every account, and unused image data is never cleaned up.
- ECS: CPU shares, task sets, ECS Exec, and EFS and FSx volumes are not supported.
- Cloud Run: worker pools, the tasks API, sidecars, scaling, secrets and volumes are not supported.
- Compute Engine: live migration, snapshots, instance templates, groups, load balancing and custom images are not supported. Networks carry no traffic, and firewall rules are only stored. Shutdown scripts and
startup-script-urldo not run. - Container Instances: exec and attach, volumes, probes, GPUs, virtual networks, init containers and identities are not supported.
The coverage page lists every limit.