Using S3 as a Jenkins Artifact Store — Storage, Lifecycles, and Deploying from S3
May 1, 2026 · 6 min read
Using S3 as a Jenkins Artifact Store
By default, Jenkins stores build artifacts on the controller or agent — wherever the build ran. This works fine for small projects but falls apart quickly: disks fill up, artifacts disappear when an agent is replaced, and deploying to a server means Jenkins needs direct access to that server. Moving artifacts to S3 addresses all three challenges.
The S3 Artifact Manager Plugin
The Artifact Manager on S3 plugin replaces Jenkins’ local artifact storage with S3.
Important: The plugin will also install several dependencies.
Ready to go in 3 easy steps:
- Install — Manage Jenkins → Plugins
- Enable — Manage Jenkins → System → Artifact Management for Builds
- Configure — Manage Jenkins → AWS → Artifact Manager Amazon S3 Bucket
Once configured, archiveArtifacts behaves exactly the same in your pipeline — the only difference is where the files end up.
- S3 Bucket Name: The bucket where artifacts will be stored
- Base Prefix: Path prefix applied to all artifacts
The plugin can use the IAM role attached to the Jenkins controller or agents when running on EC2, though credentials can also be configured explicitly. Using IAM roles is generally the preferred approach because there are no credentials to rotate, store, or accidentally expose. The role needs to be on whichever instance the build actually runs on — the controller if builds run there, the agent if you’re using build agents.
The role needs the following permissions on your artifact bucket:
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::your-bucket",
"arn:aws:s3:::your-bucket/*"
]
}
Scope it to the bucket (and optionally the Artifacts/ prefix) rather than * — Jenkins only needs access to its own artifacts, not everything in your bucket.
A dedicated bucket for Jenkins is generally worth using. IAM policies stay simple, lifecycle rules don’t have to work around other data in the bucket, and you have a clean place to add versioning or access logging later if needed.
Once configured, artifacts are stored at:
s3://your-bucket/Artifacts/{job-name}/{build-number}/artifacts/{relative-path}
For a job named my-pipeline/master, build 42, archiving out/my-api/**:
s3://your-bucket/Artifacts/my-pipeline/master/42/artifacts/out/my-api/my-api.dll
s3://your-bucket/Artifacts/my-pipeline/master/42/artifacts/out/my-api/appsettings.json
...
Every build gets its own path in S3, making it easy to inspect or retrieve artifacts directly from S3 without going through Jenkins.
Managing Costs with Lifecycle Policies
Artifacts accumulate fast. A busy pipeline archiving hundreds of files per build will rack up significant storage costs if they’re never cleaned up. S3 lifecycle policies handle this automatically.
A reasonable policy for build artifacts:
- Transition to S3 Standard-IA after 30 days — infrequent access pricing for builds you’re unlikely to redeploy
- Delete after 90 days — old builds have no operational value
Apply the policy specifically to the Artifacts/ prefix so it only targets build artifacts.
Using the AWS CLI:
aws s3api put-bucket-lifecycle-configuration \
--bucket your-bucket \
--lifecycle-configuration file://lifecycle.json
{
"Rules": [
{
"ID": "jenkins-artifact-lifecycle",
"Status": "Enabled",
"Filter": { "Prefix": "Artifacts/" },
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
}
],
"Expiration": {
"Days": 90
}
}
]
}
Adjust the numbers to match your deployment frequency and rollback requirements. If your rollback window is typically a week or less, a 90-day expiration gives you plenty of runway. If you never roll back past a few days, a 30-day expiration is fine.
⚠️ S3 Standard-IA has a minimum 30-day charge per object. If you’re archiving thousands of small files per build, transitioning to IA before deleting can cost more than just deleting directly. For short retention windows, skip the IA transition and go straight to expiration.
Deploying from S3
With artifacts in S3, the deploy model flips. Instead of Jenkins pushing files to servers, servers pull files from S3. Jenkins sends a command through AWS Systems Manager (SSM) that executes on the target instance:
aws s3 cp s3://your-bucket/Artifacts/my-pipeline/master/42/artifacts/out/my-api/ \
/home/my-app/ \
--recursive \
--exclude "*.config" \
--no-progress
sudo systemctl restart my-api
The EC2 instance needs an IAM role with read access to the artifact bucket, along with the AmazonSSMManagedInstanceCore managed policy so it can receive and execute SSM commands. No credentials on disk, no SSH keys between Jenkins and the server, no VPN or bastion required — just IAM.
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket",
"arn:aws:s3:::your-bucket/*"
]
}
Instances are targeted by EC2 tag rather than by ID or IP, so adding or replacing a server requires no pipeline changes — just apply the right tags. Jenkins triggers the command through SSM and polls until it completes:
Note: This example uses
shfor a Linux agent. On Windows, swapshforbatand replace the\line continuations with^.
def commandId = sh(script: """
aws ssm send-command \
--targets "Key=tag:Service,Values=My Service" "Key=tag:Environment,Values=PROD" \
--document-name AWS-RunShellScript \
--parameters "commands=['aws s3 cp s3://your-bucket/.../out/my-api/ /home/my-app/ --recursive && sudo systemctl restart my-api']" \
--query Command.CommandId \
--output text
""", returnStdout: true).trim()
Redeploying a Previous Build
Because every build’s artifacts live at a stable, build-numbered S3 path, rolling back or redeploying is just a matter of pointing at a different build number. Add a REDEPLOY_BUILD string parameter to your pipeline under Pipeline → This build is parameterized — leave it blank on normal builds and it falls back to the current build number:
def buildNum = (params.REDEPLOY_BUILD ?: env.BUILD_NUMBER).trim()
def s3Source = "s3://your-bucket/Artifacts/${env.JOB_NAME}/${buildNum}/artifacts/out/my-api"
env.JOB_NAME on a multibranch pipeline includes the branch name (e.g. my-pipeline/master), which is why the S3 path is structured the way it is — it matches exactly what the artifact manager plugin used when the build ran.
Set REDEPLOY_BUILD to any previous build number and the pipeline skips straight to the deploy stage using that build’s artifacts. No rebuild is required — a rollback takes only as long as the deployment itself.
⚠️ Lifecycle policies delete old artifacts. If you set a 90-day expiration and try to redeploy build 5 from six months ago, the artifacts won’t be there. Set retention based on how far back you realistically need to roll back, not just cost.
Closing Thoughts
The combination of S3 artifact storage, lifecycle policies, and pull-based deployments provides a surprisingly clean and scalable deployment model. Jenkins becomes an orchestrator — it builds, archives, and issues commands — rather than a file transfer system with direct access to every environment. The servers pull exactly what they need from S3 when told to, and IAM handles access control without any credential management.
The lifecycle policies are worth setting up on day one rather than retroactively. Once you have dozens of jobs and thousands of builds, cleaning up becomes more work than it should be. A simple 30/90-day rule handles most cases and can always be adjusted later.