On the host, aws s3 ls --profile dev works after aws sso login, but the same call from a container fails with Unable to locate credentials, or with The SSO session associated with this profile has expired or is otherwise invalid after the mount was set up, or — the common "fix" — with nothing at all, because someone pasted a long-lived access key into .env that now sits in every developer's shell history. IAM Identity Center (formerly AWS SSO) issues short-lived credentials to people, which is exactly what local development should use; containers just need a way to see them. This page gives containers SSO-derived credentials safely, as part of local secret vaults and rotation.

The goal: no long-lived access keys on laptops, credentials that expire on their own, and the same profile working on the host and in containers.

Diagnostic

Compare the credential chain on the host with what the container sees:

#!/usr/bin/env bash
set -euo pipefail
aws configure list --profile dev
aws sts get-caller-identity --profile dev --query Arn --output text
docker compose exec -T api sh -c 'env | grep -E "^AWS_" | sed -E "s/(SECRET|TOKEN)=.*/\1=***/"; ls -la ~/.aws 2>&1 | head -3'
docker compose exec -T api aws sts get-caller-identity 2>&1 | tail -1
grep -nE '^AWS_(ACCESS_KEY_ID|SECRET_ACCESS_KEY)=AKIA' .env 2>/dev/null && echo "long-lived key in .env" || true

Expected bad output:

      Name                    Value             Type    Location
   profile                      dev           manual    --profile
access_key     ****************QX7M              sso
arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_Developer_3f1c/[email protected]
AWS_REGION=eu-west-1
ls: cannot access '/root/.aws': No such file or directory
Unable to locate credentials. You can configure credentials by running "aws configure".

The host resolves credentials through SSO; the container has neither the profile configuration nor the cached SSO token, so its credential chain finds nothing.

The AWS Credential Chain in a Container Layers the AWS SDK checks for credentials, showing which ones exist on the host but not in a container. The AWS Credential Chain in a Container environment variables AWS_ACCESS_KEY_ID etc shared config profile ~/.aws/config SSO token cache ~/.aws/sso/cache container metadata not available locally
SSO credentials live in ~/.aws on the host; containers see none of it unless given access.

Root cause

AWS SDKs and the CLI search a chain of credential sources: environment variables, the shared config and credentials files for the selected profile, and then instance or container metadata endpoints. With IAM Identity Center, the profile in ~/.aws/config names an SSO session, and aws sso login writes an access token to ~/.aws/sso/cache; the SDK exchanges that token for short-lived role credentials on demand. A container has its own filesystem and environment, so none of that exists inside it, and there is no metadata endpoint on a laptop. Mounting only ~/.aws/config is not enough — the token cache is needed too — and the token itself expires (typically after 8 to 12 hours), producing the "session expired" error until the developer logs in again on the host. Long-lived IAM user keys "fix" all of this by bypassing SSO, at the cost of permanent credentials spread across laptops.

Resolution

  1. Mount the AWS directory read-only and select the profile so the SDK inside the container follows the same SSO chain as the host:
x-aws-sso: &aws-sso
  volumes:
    - ${HOME}/.aws:/home/app/.aws:ro
  environment:
    AWS_PROFILE: dev
    AWS_SDK_LOAD_CONFIG: "1"
    AWS_REGION: eu-west-1

services:
  api:
    <<: *aws-sso
    build: ./api
    user: "${UID:-1000}:${GID:-1000}"
    environment:
      HOME: /home/app

The SDK needs write access to refresh cached role credentials in some versions; if it logs a read-only error, mount ~/.aws/sso/cache and ~/.aws/cli/cache read-write and keep config read-only.

  1. Or export temporary credentials for a single run, which avoids mounting anything and suits tools that do not support SSO profiles:
#!/usr/bin/env bash
set -euo pipefail
aws sso login --profile dev
eval "$(aws configure export-credentials --profile dev --format env)"
docker compose up -d --force-recreate api
docker compose exec -T api aws sts get-caller-identity --query Arn --output text

export-credentials prints short-lived AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN; Compose passes them through if the service lists them with empty values (AWS_SESSION_TOKEN:), which forwards the host value.

  1. Handle expiry explicitly in the bootstrap or task runner, so developers see a clear prompt instead of an SDK error:
#!/usr/bin/env bash
set -euo pipefail
if ! aws sts get-caller-identity --profile dev >/dev/null 2>&1; then
  echo "AWS SSO session expired; opening browser to sign in"
  aws sso login --profile dev
fi
  1. Delete long-lived keys from .env files and deactivate the IAM users behind them, after confirming nothing else uses them.
Mount the Cache or Export Keys? Decision diagram choosing between mounting the AWS directory and exporting temporary credentials. Mount the Cache or Export Keys? Does the tool support SSO profiles? yes, SDK v3 or boto3 mount ~/.aws read-only no, or one-off run export temporary keys
Mounting follows the SSO chain automatically; exporting suits tools without SSO support.

Expected output

$ docker compose exec -T api aws sts get-caller-identity --query Arn --output text
arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_Developer_3f1c/[email protected]
$ docker compose exec -T api aws s3 ls s3://acme-dev-uploads | head -2
2026-09-12 10:14:22      18231 invoices/2026-09-12.pdf
$ grep -cE '^AWS_ACCESS_KEY_ID=AKIA' .env || echo "no long-lived keys in .env"
no long-lived keys in .env

The container assumes the developer's SSO role, API calls succeed with short-lived credentials, and no permanent keys remain in local configuration.

CloudTrail now records each call under the developer's own identity rather than a shared IAM user, which makes audits and incident investigations straightforward, and access ends automatically when the SSO session expires or when the person leaves and is removed from the identity provider. That is the security case for doing this even when a pasted key "works".

Prevention

  1. Block long-lived keys in commits and env files with the secret scanner's AWS rules, as in blocking committed secrets with a gitleaks pre-commit hook.

  2. Prefer emulators for most local work so AWS credentials are needed only for tasks that genuinely touch real accounts; see running S3 and SQS locally with LocalStack.

  3. Check the session in make doctor and print the login command when it has expired.

Long-Lived Keys vs SSO Credentials Comparison of IAM user access keys in .env against short-lived credentials from IAM Identity Center. Long-Lived Keys vs SSO Credentials IAM user keys in .env SSO short-lived credentials never expire expire in hours shared or copied tied to one identity rotation is manual nothing to rotate leak is long-term risk leak expires quickly
SSO credentials expire by themselves and are tied to a person, not a shared key.

Platform caveats

macOS (Docker Desktop): ${HOME}/.aws resolves to /Users/<name>/.aws, which Docker Desktop shares by default. If the home directory is excluded from file sharing, the mount is empty.

WSL2: run aws sso login inside WSL so the cache is in the Linux home directory that containers mount; a login on the Windows side writes to a different directory.

Rootless or non-root containers: mount to the container user's home and set HOME accordingly, or the SDK looks in /root/.aws.

Dev containers: the AWS CLI Feature plus a mounts entry for ~/.aws achieves the same result inside a dev container.

Rollback

Remove the mount and environment block; containers fall back to whatever credentials are in their environment:

#!/usr/bin/env bash
set -euo pipefail
git checkout HEAD~1 -- compose.yaml
docker compose up -d --force-recreate api

Frequently Asked Questions

Why does the container say "Unable to locate credentials" when the host works?

The container cannot see ~/.aws or the SSO token cache, and there is no metadata endpoint locally. Mount ~/.aws read-only and set AWS_PROFILE, or export temporary credentials into the container's environment.

What does "The SSO session ... has expired" mean?

The cached SSO token is past its lifetime. Run aws sso login --profile dev on the host; mounted containers pick up the new token without restarting.

Is mounting ~/.aws into containers safe?

It gives the container the same access the developer has, for as long as the session lasts. Mount it read-only, only into services that need AWS, and prefer emulators for services that do not.

Can CI use the same approach?

CI should use OIDC federation to assume a role directly (for example aws-actions/configure-aws-credentials with role-to-assume), which issues short-lived credentials without any stored keys.