Troubleshooting¶
Fixes for the problems people hit most often with DevOps Images. Find your symptom below, then open the matching question.
flowchart TD
Q(["Where does it fail?"]) --> P["Pulling the image"]
Q --> R["Starting or running<br/>the container"]
Q --> A["Cloud or Git<br/>authentication"]
Q --> T["A tool inside<br/>the container"]
Q --> AI["AI CLI login"]
Q --> B["Building the image<br/>yourself"]
Q --> D["Docs site preview"]
classDef neutral fill:#334155,stroke:#1e293b,color:#fff
classDef base fill:#0891b2,stroke:#0e7490,color:#fff
classDef aws fill:#ea7a0c,stroke:#c2410c,color:#fff
classDef gcp fill:#2563eb,stroke:#1d4ed8,color:#fff
classDef ai fill:#d97706,stroke:#b45309,color:#fff
classDef all fill:#059669,stroke:#047857,color:#fff
class Q neutral
class P,R base
class A aws
class T gcp
class AI ai
class B,D all
-
Access denied, unknown tags, rate limits, wrong architecture
-
Root-owned files, permissions, exits, disk space
-
AWS, Google Cloud and SSH credentials
-
Command not found, kubectl, GKE, Terraform locks, Trivy, pip
-
Claude Code, Codex, Copilot and Antigravity logins
-
Download failures, build args, memory, cross-architecture
Pulling images¶
pull access denied, manifest unknown or not found
-
Check the path. The three registries use different paths:
-
Check the tag. Only
latest,1.0.<7-char sha>and1.0.<sha>-amd64/-arm64exist. There is no1.0tag and no semver tags. To list published tags on GHCR (the packages API needs authentication even for public images, and yourghtoken needs theread:packagesscope:gh auth refresh -s read:packages): -
Try another registry if one is having an outage:
The images are public, so you shouldn't need docker login to pull. If you are logged in with an expired token, docker logout ghcr.io and try again.
toomanyrequests: You have reached your pull rate limit (Docker Hub)
Docker Hub throttles anonymous pulls. Either:
- pull from GHCR instead:
docker pull ghcr.io/jinalshah/devops/images/all-devops:latest, or -
log in to Docker Hub (
docker login) so the higher authenticated limit applies. In GitHub Actions:
Pulls are slow or time out
The images are about 1.5 to 1.6 GB compressed, so a first pull takes a while. In CI, retry transient failures:
for i in 1 2 3; do
docker pull ghcr.io/jinalshah/devops/images/all-devops:latest && break
echo "Attempt $i failed, retrying..."
sleep $((i * 10))
done
Pick the smallest image that has what you need (aws-devops or gcp-devops), and on self-hosted runners keep the image cached between jobs.
exec format error or a platform mismatch warning
The images are published for linux/amd64 and linux/arm64, and Docker normally picks the right one. If you get the wrong one (for example after pulling a -amd64 tag on Apple Silicon):
# What did you get?
docker run --rm ghcr.io/jinalshah/devops/images/all-devops:latest uname -m
# x86_64 = amd64, aarch64 = arm64
# Force the native platform
docker pull --platform linux/arm64 ghcr.io/jinalshah/devops/images/all-devops:latest
Use the plain 1.0.<sha> tag (the multi-arch manifest) rather than an arch-suffixed one unless you really need a specific architecture.
Running containers¶
Files created in the container are owned by root on the host
The container runs as root, so files it writes to a bind mount belong to root on Linux hosts.
Fix ownership in the same run:
docker run --rm -v "$PWD":/srv -w /srv \
ghcr.io/jinalshah/devops/images/all-devops:latest \
sh -c 'terraform fmt -recursive && chown -R '"$(id -u):$(id -g)"' /srv'
Why not run with --user?
Running as a non-root user isn't a drop-in fix for this image. /root is only readable by root, and terraform (a tfswitch symlink into /root/.terraform.versions) and claude (in /root/.local/bin) live under it, so they fail for other users. Run as root and fix ownership afterwards, as above.
Docker Desktop on macOS and Windows maps ownership for you, so this mostly affects Linux hosts.
Permission denied on a mounted file or directory
- Make the script executable on the host (
chmod +x script.sh), or run it withbash script.sh. -
On SELinux hosts (Fedora, RHEL, Rocky), relabel the mount with
:z(shared) or:Z(private):
The container exits immediately
The default command is /bin/zsh, which exits straight away without a terminal. Use -it for an interactive shell, or give it a command:
no space left on device
Each image is about 4.6 to 5 GB unpacked, and old tags add up quickly.
docker system df # what is using space
docker image prune -a # remove unused images
docker system prune # remove stopped containers, networks and dangling images
On Docker Desktop, you can also raise the disk limit in Settings → Resources.
Aliases like tf or k don't work
Aliases are defined in ~/.zshrc and ~/.bashrc, so they only exist in interactive Zsh and Bash shells. They aren't available in docker run <image> <command>, CI steps, Fish, or when you override HOME. Use the full command there. See the alias list.
Authentication¶
AWS: Unable to locate credentials
Only aws-devops and all-devops include the AWS CLI. Give the container credentials in one of these ways:
docker run --rm -v ~/.aws:/root/.aws \
-e AWS_PROFILE=my-profile \
ghcr.io/jinalshah/devops/images/aws-devops:latest \
aws sts get-caller-identity
For IAM Identity Center profiles, the mount needs to be writable so aws sso login can refresh the token cache.
On EC2, ECS or EKS the SDK picks up the instance, task or pod role automatically. On EC2 with IMDSv2, a container on a bridge network may need the instance's metadata hop limit raised to 2.
Google Cloud: You do not currently have an active account selected
Only gcp-devops and all-devops include gcloud.
gcloud ignores GOOGLE_APPLICATION_CREDENTIALS for its own commands, so activate the key explicitly:
docker run --rm -v /path/to/sa.json:/secrets/sa.json:ro \
ghcr.io/jinalshah/devops/images/gcp-devops:latest \
sh -c 'gcloud auth activate-service-account --key-file=/secrets/sa.json && gcloud projects list'
Keep GOOGLE_APPLICATION_CREDENTIALS for Terraform and the client libraries, which do read it.
Git: Permission denied (publickey)
Mount your SSH directory read-only:
docker run -it --rm -v ~/.ssh:/root/.ssh:ro -v "$PWD":/srv -w /srv \
ghcr.io/jinalshah/devops/images/all-devops:latest
If SSH complains about key permissions, fix them on the host (chmod 600 ~/.ssh/id_ed25519). If your key lives in an agent (for example 1Password or macOS Keychain) rather than a file, use HTTPS with gh auth login instead.
Tools inside the container¶
command not found: aws or command not found: gcloud
You're probably in the wrong image. aws and session-manager-plugin are only in all-devops aws-devops, and gcloud, gsutil and bq are only in all-devops gcp-devops.
Some tools aren't in any image: the Docker CLI, standalone kustomize (use kubectl apply -k), yq, terraform-docs and gcloud alpha. The tool reference lists what's included.
kubectl: The connection to the server localhost:8080 was refused
kubectl has no kubeconfig. Mount yours, or generate one inside the container:
# Mount your host kubeconfig
docker run -it --rm -v ~/.kube:/root/.kube \
ghcr.io/jinalshah/devops/images/all-devops:latest
# Or point at a specific file
docker run -it --rm -v ~/.kube/prod.yaml:/kubeconfig:ro -e KUBECONFIG=/kubeconfig \
ghcr.io/jinalshah/devops/images/all-devops:latest
# EKS
aws eks update-kubeconfig --name my-cluster --region eu-west-2
If your kubeconfig points at 127.0.0.1 (kind, minikube, Docker Desktop), that address means the container itself. Use --network host on Linux, or host.docker.internal on Docker Desktop.
GKE: gcloud container clusters get-credentials or kubectl fails
gke-gcloud-auth-plugin is installed in all-devops and gcp-devops, so GKE works without extra setup. If it still fails:
# 1. Is gcloud authenticated, and on the right project?
gcloud auth list
gcloud config get-value project
# 2. Use the cluster's real location: --region for regional clusters, --zone for zonal ones
gcloud container clusters list
gcloud container clusters get-credentials my-cluster --region europe-west2 --project my-project
# 3. Is the plugin there?
gke-gcloud-auth-plugin --version
Permission deniedor403: your account needs at leastroles/container.clusterViewerto fetch credentials, plus Kubernetes RBAC rights for what you then run.executable gke-gcloud-auth-plugin not found: you're using a kubeconfig written on another machine or in an older image. Runget-credentialsagain inside this container.- Private clusters: the container needs network access to the control-plane endpoint (VPN, authorised networks or DNS-based endpoint).
Terraform: Error acquiring the state lock
Another run holds the lock, or a crashed run left it behind. Make sure nothing else is running, then:
For S3 backends, use_lockfile = true is the current locking option; dynamodb_table is deprecated. Check that the container has credentials for the backend (see Authentication).
Trivy: the first scan is slow or can't download its database
No vulnerability database is baked into the image, so each fresh container downloads it. Cache it between runs:
docker run --rm -v ~/.cache/trivy:/root/.cache/trivy -v "$PWD":/srv -w /srv \
ghcr.io/jinalshah/devops/images/all-devops:latest trivy fs .
If the cache is corrupt, reset it with trivy clean --all. Behind a proxy, pass HTTPS_PROXY into the container.
pip installs a package but Python can't import it
A bare pip may belong to the distribution's Python rather than the default Python 3.14. Always use:
Ansible: the playbook: playbook.yml could not be found
Your project isn't mounted, or the working directory isn't set. Use the standard mount:
AI CLI logins¶
Each CLI keeps its login in a directory under /root, so mount it to keep the login between containers:
docker run -it --rm \
-v "$PWD":/srv -w /srv \
-v ~/.claude:/root/.claude \
-v ~/.codex:/root/.codex \
-v ~/.copilot:/root/.copilot \
-v ~/.gemini:/root/.gemini \
ghcr.io/jinalshah/devops/images/all-devops:latest
Claude Code asks you to log in every time
- Interactive: run
claude, then/login.claude auth statusshows the current state. - CI or scripts: set
ANTHROPIC_API_KEY, orCLAUDE_CODE_OAUTH_TOKEN(created withclaude setup-token, which needs a Claude subscription). There is noCLAUDE_API_KEY. - Persist it: mount
~/.claude, and also~/.claude.jsonif you want the global state. - Script hangs or prints a UI: add
-p. Without it,claudestarts the interactive interface even when output is redirected.
Codex: not signed in
codex login # ChatGPT sign-in
codex login --device-auth # no browser in the container
printenv OPENAI_API_KEY | codex login --with-api-key
codex login status
In CI, set CODEX_API_KEY and run codex exec "...". Credentials live in ~/.codex/auth.json, so mount ~/.codex and treat it like a password.
Copilot CLI: authentication fails
- Interactive: run
copilot, then/login(a device-code flow that works without a browser). - Token: set
COPILOT_GITHUB_TOKEN(orGH_TOKEN/GITHUB_TOKEN) to a fine-grained personal access token with the "Copilot Requests" permission. Classicghp_tokens are rejected. - You need an active Copilot subscription.
- Non-interactive runs need
--allow-all-tools:copilot -p "..." --allow-all-tools. - The image has the standalone
copilotCLI, not the oldgh copilotextension.
Antigravity CLI (agy): can't sign in
Antigravity CLI replaced Google's Gemini CLI, and the gemini command isn't in the image.
- Interactive: run
agy. With no browser it prints a URL; open it on your machine, sign in with Google, and paste the code back. Use/loginand/logoutinsideagy. There's noagy loginsubcommand. - Persist it: mount
~/.gemini. In containers there's no keyring, so tokens are stored in files there. -
Gemini API key (headless): exporting
GEMINI_API_KEYalone isn't enough. Also create~/.gemini/antigravity-cli/settings.jsoncontaining: -
Application Default Credentials: run
gcloud auth application-default login(or setGOOGLE_APPLICATION_CREDENTIALS), thenexport AGY_ADC_AUTH=true. - Headless failures exit with code 3 and print
AGY_ERROR: {...}on stderr, which usually names the problem.
See AI CLI setup for full configuration.
Building images yourself¶
A download step fails during the build
Most failures are transient upstream downloads, so retry first. Then:
The build fails after overriding a version with --build-arg
- Make sure the version exists on the tool's release page, and pass it without a leading
v(for example0.68.14, notv0.68.14). -
Change one argument at a time:
-
Python needs both arguments, a full version and the matching binary name:
The build is Killed or runs out of memory
Compiling Python with optimisations is the heaviest step. Give Docker more memory (Docker Desktop: Settings → Resources, 8 GB or more is comfortable), and build the shared base once so later targets reuse its cache:
Building for another architecture
CI builds each architecture natively. Locally, cross-building works through emulation but is slow:
docker buildx create --name multiarch --use
# Load a single platform into your local image store
docker buildx build --platform linux/arm64 --target all-devops -t all-devops:arm64 --load .
Multi-platform builds (--platform linux/amd64,linux/arm64) can't be loaded into the classic image store, so push them to a registry with --push instead. The containerd image store (the default in Docker Desktop and for new installs of Docker Engine 29 and later) can load them.
Docs site preview¶
zensical: command not found
Zensical is also pre-installed in every image, so you can preview the docs from a container:
Changes don't show up
Stop the server with Ctrl+C, then rebuild from scratch and serve again:
If the page still looks stale, hard-refresh the browser.
Still stuck?¶
Collect these details and open an issue:
docker version
uname -m
docker image inspect --format '{{index .RepoDigests 0}}' ghcr.io/jinalshah/devops/images/all-devops:latest
- The image and tag (or digest) you used
- Your host OS and architecture
- The exact command you ran
- The full error output
- What you expected to happen