How to Run a Cloud Provider CLI in a Docker Container: AWS, Azure, and GCP Setup Guide

How to Run a Cloud Provider CLI in a Docker Container

I got tired, a long time ago, of installing the AWS CLI on one laptop, the Azure CLI on another, and then discovering version mismatches between what’s on my machine and what’s on a teammate’s machine when we’re debugging the same deployment. The fix I landed on, and the one most DevOps teams eventually land on too, is to stop installing cloud CLIs locally altogether and run them from containers instead.

This guide covers exactly how to do that for AWS, Azure, and GCP — including authentication, credential persistence, and building your own multi-cloud CLI image.

Why Run Cloud CLIs in Containers?

Before the how, the why:

  • No local pollution. Python version conflicts, Node dependency clashes, and CLI tool version drift never touch your host machine.
  • Reproducibility. Everyone on the team runs the exact same CLI version, pinned by image tag.
  • Portability. The same container works identically on Linux, macOS, and Windows (via Docker Desktop).
  • CI/CD friendliness. The same image you use locally can be dropped straight into a GitLab CI or GitHub Actions job.
  • Easy version pinning. Need to test against an older CLI version for a legacy pipeline? Just pull a different tag.

AWS CLI in a Container

AWS publishes an official CLI image.

docker pull amazon/aws-cli:2.17.0

Running a Single Command

docker run --rm amazon/aws-cli:2.17.0 --version

Expected output:

aws-cli/2.17.0 Python/3.11.6 Linux/6.1.0 exe/x86_64.docker

Passing Credentials

The cleanest way is to mount your existing ~/.aws directory as read-only:

docker run --rm -it \
  -v ~/.aws:/root/.aws:ro \
  amazon/aws-cli:2.17.0 s3 ls

Expected output (assuming you have buckets):

2026-01-15 09:32:11 my-app-logs
2026-03-02 14:11:47 terraform-state-prod

Alternatively, pass credentials as environment variables — useful in CI where you don’t want a mounted config directory:

docker run --rm \
  -e AWS_ACCESS_KEY_ID=AKIA... \
  -e AWS_SECRET_ACCESS_KEY=... \
  -e AWS_DEFAULT_REGION=us-east-1 \
  amazon/aws-cli:2.17.0 ec2 describe-instances

Creating a Shell Alias

To make the container feel like a native binary, add this to your .bashrc or .zshrc:

alias aws='docker run --rm -it -v ~/.aws:/root/.aws:ro -v "$PWD":/aws amazon/aws-cli:2.17.0'

Now aws s3 ls works exactly like the local binary would, transparently running inside a container.

Azure CLI in a Container

Microsoft publishes an official Azure CLI image.

docker pull mcr.microsoft.com/azure-cli:2.63.0

Interactive Login

docker run -it mcr.microsoft.com/azure-cli:2.63.0 az login

This prints a device-code URL and code:

To sign in, use a web browser to open the page https://microsoft.com/devicelogin
and enter the code ABCD1234 to authenticate.

Persisting Login Across Container Runs

By default, once the container exits, your login session is gone. Persist the ~/.azure config directory with a named volume:

docker volume create azure-cli-config

docker run -it \
  -v azure-cli-config:/root/.azure \
  mcr.microsoft.com/azure-cli:2.63.0 az login

Subsequent runs reuse that same volume and stay authenticated:

docker run --rm \
  -v azure-cli-config:/root/.azure \
  mcr.microsoft.com/azure-cli:2.63.0 az account show

Expected output:

{
  "environmentName": "AzureCloud",
  "id": "b1234567-89ab-cdef-0123-456789abcdef",
  "isDefault": true,
  "name": "Pay-As-You-Go",
  "user": {
    "name": "you@example.com",
    "type": "user"
  }
}

Service Principal Login (Non-Interactive, CI-Friendly)

docker run --rm mcr.microsoft.com/azure-cli:2.63.0 az login \
  --service-principal \
  -u $AZURE_CLIENT_ID \
  -p $AZURE_CLIENT_SECRET \
  --tenant $AZURE_TENANT_ID

Google Cloud CLI (gcloud) in a Container

Google publishes official images under gcr.io/google.com/cloudsdktool/google-cloud-cli.

docker pull gcr.io/google.com/cloudsdktool/google-cloud-cli:latest

Authenticating

docker run -it \
  -v gcloud-config:/root/.config/gcloud \
  gcr.io/google.com/cloudsdktool/google-cloud-cli:latest \
  gcloud auth login

Using a Service Account Key

For automation, mount a service account JSON key file and activate it:

docker run --rm \
  -v /path/to/key.json:/tmp/key.json:ro \
  gcr.io/google.com/cloudsdktool/google-cloud-cli:latest \
  gcloud auth activate-service-account --key-file=/tmp/key.json

Listing Compute Instances

docker run --rm \
  -v gcloud-config:/root/.config/gcloud \
  gcr.io/google.com/cloudsdktool/google-cloud-cli:latest \
  gcloud compute instances list

Expected output:

NAME        ZONE           MACHINE_TYPE   STATUS
web-server  us-central1-a  e2-medium      RUNNING

Building a Single “Multi-Cloud” CLI Image

Rather than switching between three separate images, many teams build one image with all three CLIs installed, so a single container can talk to AWS, Azure, and GCP in the same script.

FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y \
    curl unzip python3 python3-pip apt-transport-https \
    lsb-release gnupg ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# AWS CLI v2
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
    unzip awscliv2.zip && ./aws/install && rm -rf awscliv2.zip aws

# Azure CLI
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash

# Google Cloud CLI
RUN curl -sSL https://sdk.cloud.google.com | bash -s -- --disable-prompts && \
    ln -s /root/google-cloud-sdk/bin/gcloud /usr/local/bin/gcloud

WORKDIR /workspace
ENTRYPOINT ["/bin/bash"]

Build and run it:

docker build -t multicloud-cli:1.0 .
docker run --rm -it multicloud-cli:1.0

Inside the container, verify all three:

aws --version
az --version
gcloud --version

Using Compose to Manage Credential Volumes

If you regularly switch between clouds, a docker-compose.yml makes this cleaner than remembering long docker run commands:

services:
  aws-cli:
    image: amazon/aws-cli:2.17.0
    volumes:
      - ~/.aws:/root/.aws:ro
    entrypoint: aws

  azure-cli:
    image: mcr.microsoft.com/azure-cli:2.63.0
    volumes:
      - azure-config:/root/.azure

  gcloud-cli:
    image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest
    volumes:
      - gcloud-config:/root/.config/gcloud

volumes:
  azure-config:
  gcloud-config:

Run any of them with:

docker compose run --rm aws-cli s3 ls
docker compose run --rm azure-cli az group list
docker compose run --rm gcloud-cli gcloud projects list

Understanding What’s Actually Happening Under the Hood

It’s worth being clear about what containerizing a CLI does and doesn’t change. The aws, az, and gcloud binaries are ordinary programs — running them inside a container doesn’t make them faster or give them special access to anything. What changes is isolation: the container gets its own filesystem layer, its own set of installed dependencies, and (unless you mount your credential directory in) no access to your host’s files at all by default. That last point is exactly why the -v ~/.aws:/root/.aws:ro pattern matters — without it, the CLI inside the container has no way to find your credentials, because containers don’t inherit anything from the host filesystem unless you explicitly mount it.

This also explains why --rm shows up in almost every example above: without it, Docker leaves a stopped container behind after every single command, and running dozens of aws s3 ls calls a day would otherwise leave dozens of dead containers cluttering docker ps -a.

Handling Output Files and Local Directories

A common need is running a CLI command that writes output to a local file — for example, downloading an S3 object or exporting a Terraform plan. Since the container has its own isolated filesystem, you need to mount your working directory in as well as your credentials:

docker run --rm \
  -v ~/.aws:/root/.aws:ro \
  -v "$PWD":/workspace \
  -w /workspace \
  amazon/aws-cli:2.17.0 s3 cp s3://my-bucket/report.csv ./report.csv

Expected output:

download: s3://my-bucket/report.csv to ./report.csv

The -w /workspace flag sets the container’s working directory to match the mounted volume, so relative paths in the command behave the way you’d expect.

Integrating into CI/CD Pipelines

The exact same images work as pipeline steps. A GitHub Actions example using the AWS CLI container directly (rather than the aws-actions/configure-aws-credentials action) looks like this:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Sync to S3
        run: |
          docker run --rm \
            -e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
            -e AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
            -e AWS_DEFAULT_REGION=us-east-1 \
            -v "$PWD":/workspace -w /workspace \
            amazon/aws-cli:2.17.0 s3 sync ./dist s3://my-app-bucket

The advantage over installing the CLI directly in the CI runner is version pinning — the pipeline behaves identically regardless of what’s baked into the runner image, and upgrading the CLI version is a one-line tag change.

Performance Considerations

Containerized CLIs add a small amount of overhead versus a natively installed binary — mainly image pull time on first use (subsequent runs use the cached image) and a few hundred milliseconds of container startup per invocation. For interactive use this is imperceptible; for scripts issuing hundreds of CLI calls in a tight loop, consider starting a single long-lived container and issuing multiple commands inside it via docker exec, rather than paying the startup cost per call:

docker run -d --name aws-session --entrypoint sleep \
  -v ~/.aws:/root/.aws:ro amazon/aws-cli:2.17.0 infinity

docker exec aws-session aws s3 ls
docker exec aws-session aws ec2 describe-instances

docker stop aws-session && docker rm aws-session

Security Considerations

  • Never bake credentials into an image. Layers are cached and can leak secrets even after deletion from later layers.
  • Prefer named volumes or bind mounts for credential storage over environment variables when running interactively, since environment variables are visible via docker inspect and process listings on the host.
  • Use short-lived tokens where possible — AWS STS assume-role sessions, Azure managed identities, and GCP workload identity federation all reduce the blast radius of a leaked credential.
  • Mount credential directories read-only (:ro) whenever the CLI doesn’t need to write back to them.
  • Rotate CI service principal / service account credentials regularly, and scope their IAM permissions to the minimum required for the pipeline.

Troubleshooting

  • “Unable to locate credentials” (AWS) — double check the mount path matches the CLI’s expected config location (/root/.aws for the root user inside the container, not /home/user/.aws unless you changed the container’s user).
  • Azure login opens a browser but the container has no browser — this is expected; use the device code flow (az login without --service-principal) shown above, or use a service principal in headless environments.
  • gcloud commands fail with PERMISSION_DENIED — verify the active account/project with gcloud config list before assuming the credentials themselves are bad.
  • Volume permissions errors on Linux hosts — if the container runs as a non-root user internally, you may need to chown the mounted directory or pass --user $(id -u):$(id -g) to match host file ownership.

Summary

Running cloud CLIs inside Docker containers removes an entire category of “works on my machine” problems from multi-cloud DevOps workflows. Official images exist for AWS, Azure, and GCP, authentication can be persisted across runs using named volumes, and it’s straightforward to combine all three into a single multi-cloud utility image for scripts that need to touch more than one provider. The same pattern scales cleanly into CI/CD, where these exact images can be reused as pipeline steps.

References

  • AWS CLI Docker image: https://hub.docker.com/r/amazon/aws-cli
  • Azure CLI Docker image: https://learn.microsoft.com/en-us/cli/azure/run-azure-cli-docker
  • Google Cloud CLI Docker image: https://cloud.google.com/sdk/docs/downloads-docker
  • Docker Compose file reference: https://docs.docker.com/reference/compose-file/

Total
2
Shares

Leave a Reply

Previous Post
How to Starting a Docker Host on Microsoft Azure

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

Next Post
How to Get Detailed Information About a Container with docker inspect

How to Get Detailed Information About a Container with Docker Inspect: Complete Guide

Related Posts