aws s3api put-object: Upload Objects with Full Control
aws s3api put-object uploads one object to S3 with precise control over content type, server-side encryption, cache headers, and metadata that the high-level s3 cp does not always expose.
For a single artifact where the exact Content-Type or encryption matters (a JSON manifest, a sourcemap, a signed blob), s3api put-object gives the low-level control that aws s3 cp abstracts away.
What it does
aws s3api put-object uploads --body to --bucket/--key and lets you set --content-type, --cache-control, --metadata, --server-side-encryption, and --acl explicitly. It returns the ETag and any version ID.
Common usage
aws s3api put-object \
--bucket my-artifacts \
--key builds/$GITHUB_SHA/manifest.json \
--body manifest.json \
--content-type application/json \
--cache-control 'no-cache' \
--server-side-encryption aws:kms \
--ssekms-key-id alias/ci-artifactsOptions
| Flag | What it does |
|---|---|
| --bucket / --key | Destination bucket and object key (required) |
| --body <file> | Local file to upload |
| --content-type <mime> | Set MIME type (cp guesses; this is explicit) |
| --cache-control <hdr> | Cache-Control response header |
| --metadata k=v,... | User metadata (x-amz-meta-*) |
| --server-side-encryption AES256|aws:kms | SSE mode |
| --ssekms-key-id <key> | KMS key for aws:kms encryption |
| --acl <canned-acl> | Canned ACL (often blocked by bucket policy) |
In CI
Use put-object when Content-Type must be exact (browsers ignore mis-typed assets) or when a bucket requires aws:kms. Many buckets now set Object Ownership to bucket-owner-enforced, which rejects --acl; omit ACLs and rely on bucket policy. Reference the KMS key by alias so rotation does not break the pipeline.
Common errors in CI
"An error occurred (AccessDenied) when calling the PutObject operation" can be a missing s3:PutObject, a Block Public Access conflict with --acl, or a missing kms:GenerateDataKey on the KMS key. "An error occurred (AccessDenied) ... The bucket does not allow ACLs" appears when Object Ownership is bucket-owner-enforced; drop --acl. "NoSuchBucket" means wrong bucket/region. "KMS.NotFoundException" means a bad --ssekms-key-id.