Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Pulumi By James Joyner IV · · 8 min read Last reviewed Jul 2026

Pulumi Error: 'AccessDenied' on a Self-Managed S3/GCS/Azure Backend

Quick answer

Fix Pulumi self-managed backend 'AccessDenied' / '403' errors reading or writing state in S3, GCS, or Azure Blob: diagnose IAM/bucket policy, credentials, and region mismatches.

  • #pulumi
  • #iac
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Pulumi error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Exact Error Message

error: getting secrets manager: blob (key ".pulumi/stacks/app/dev.json") (code=Unknown): AccessDenied:
Access Denied
    status code: 403, request id: 8FQ2..., host id: ...

Depending on the backend you may instead see:

error: could not get cloud url s3://my-pulumi-state: blob (code=PermissionDenied): operation error S3:
ListObjectsV2, https response error StatusCode: 403
error: unable to list stacks: googleapi: Error 403: ... does not have storage.objects.list access
to the Google Cloud Storage bucket.

What It Means

With a self-managed backend, Pulumi stores each stack’s checkpoint as a blob (for example .pulumi/stacks/<project>/<stack>.json) in an object store you own — S3, GCS, or Azure Blob. Every pulumi command that touches state must list, read, and write those objects. A 403 AccessDenied means the credentials Pulumi resolved do not have the required object-level permissions on that bucket or container, so the operation is refused before any infrastructure work happens.

This is purely an object-store authorization problem. It is unrelated to the AWS/GCP/Azure resources your program manages; even an empty program will fail if it cannot read its own state blob.

Common Causes

  • The active credentials (IAM role/user, GCP service account, Azure identity) lack s3:ListBucket + s3:GetObject + s3:PutObject (or the GCS/Azure equivalents) on the state bucket.
  • A restrictive bucket policy, ACL, or PublicAccessBlock/VPC endpoint policy denies the caller.
  • The AWS_PROFILE/AWS_REGION (or GOOGLE_APPLICATION_CREDENTIALS, AZURE_STORAGE_ACCOUNT) in the environment points at the wrong identity or region.
  • S3 bucket in a different region than the SDK default, causing a redirect that presents as denied.
  • Object encryption with a KMS key the caller cannot use (kms:Decrypt/kms:GenerateDataKey missing).
  • In CI, a short-lived token expired or the OIDC role trust policy does not include the workflow.

Diagnostic Commands

Confirm which login (backend URL) Pulumi is using:

pulumi whoami -v

Verify the same credentials can list the state prefix directly with the cloud CLI (S3 example):

aws s3 ls s3://my-pulumi-state-bucket/.pulumi/stacks/ --region us-east-1

For GCS and Azure, test the equivalent list operation:

gsutil ls gs://my-pulumi-state-bucket/.pulumi/stacks/
az storage blob list --account-name mypulumistate --container-name state --auth-mode login -o table

Check the caller identity actually in effect:

aws sts get-caller-identity

Step-by-Step Resolution

  1. Log into the correct backend URL and confirm it matches where state actually lives:
pulumi login s3://my-pulumi-state-bucket
  1. Attach an IAM policy granting the object-level actions Pulumi needs on that bucket:
{
  "Effect": "Allow",
  "Action": ["s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
  "Resource": [
    "arn:aws:s3:::my-pulumi-state-bucket",
    "arn:aws:s3:::my-pulumi-state-bucket/*"
  ]
}
  1. Pin the region and profile explicitly so the SDK does not resolve the wrong identity or endpoint:
export AWS_REGION=us-east-1
export AWS_PROFILE=pulumi-ci
  1. If the bucket is KMS-encrypted, grant the caller key permissions (kms:Decrypt, kms:GenerateDataKey) on the CMK, then confirm a direct read succeeds:
aws s3 cp s3://my-pulumi-state-bucket/.pulumi/stacks/app/dev.json - >/dev/null
  1. Re-run a lightweight Pulumi command to confirm state access is restored:
pulumi stack ls

Prevention

  • Give CI a dedicated, least-privilege role scoped to exactly the state bucket and prefix, and prefer OIDC over long-lived keys.
  • Keep the backend URL, region, and credential profile pinned in your pipeline environment so runs are deterministic.
  • If you use KMS/CMEK on the state store, include key-usage permissions in the same policy that grants object access.
  • Enable versioning on the state bucket so a bad write can be recovered, and lock down public access.
  • Document the exact backend URL in your README so contributors run pulumi login against the right store.
  • could not decrypt configuration value — a secrets passphrase/provider problem, not a backend permission one.
  • the stack's secrets provider does not match — secrets provider mismatch after moving backends.
  • unknown stack — a StackReference name issue, unrelated to bucket access.
  • blob (code=NotFound) — the bucket or key does not exist, versus a 403 where it exists but is forbidden.

Frequently Asked Questions

Why does pulumi stack ls fail with 403 but my app has no resources yet? Listing stacks requires reading the state prefix in the bucket. If the credentials cannot list/read those objects, Pulumi fails before it ever looks at your program.

How do I know which credentials Pulumi is using? Run pulumi whoami -v for the backend URL, then aws sts get-caller-identity (or the GCP/Azure equivalent) to see the identity actually resolved from the environment.

Do I need bucket-level and object-level permissions? Yes. s3:ListBucket is granted on the bucket ARN, while GetObject/PutObject are granted on the bucket/* object ARN. Missing the bucket-level list action is a common cause of 403 on stack ls.

Why does it work locally but fail in CI? CI typically assumes a different role; make sure its policy covers the state bucket and prefix. Reusable backend-setup prompts are in the Pulumi prompt library.

Can a KMS key cause AccessDenied even when S3 permissions are correct? Yes. If the state objects are encrypted with a CMK, the caller also needs kms:Decrypt and kms:GenerateDataKey on that key. See more Pulumi guides.

Free download · 368-page PDF

Fixed it? Get 500 Pulumi & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.