A SaaS team has a working application, CodeBuild stitched together by hand, and a deploy script someone runs from their laptop after a Slack message. They want a real pipeline before the next compliance review. They have looked at the AWS reference architecture diagrams and found them too generic to copy — the diagrams show “Build” and “Deploy” boxes, not the actual aws cloudformation parameter that makes deploy stage 4b run only when stage 4a passed. The pipeline below is a working CodePipeline definition for a typical SaaS — Source from a Git provider, parallel test stages, staging auto-deploy, manual approval, prod blue/green via CodeDeploy, and a post-deploy validation gate.
What’s happening
A complete CodePipeline for a SaaS has six stages, in this order:
- Source — pulls the commit from CodeCommit or GitHub via CodeStar Connections. Output: a single artifact zipped from the repo.
- Build — CodeBuild compiles, lints, runs unit tests, produces the deployable artifact (container image to ECR, lambda zip to S3, or both). Output: build artifact plus a manifest.
- Test — three parallel CodeBuild actions: integration tests against an ephemeral environment, security scan (Snyk/Trivy/AWS Inspector), and infrastructure plan (
terraform planorcdk diff). All three must succeed. - Deploy-Staging — CodeDeploy or CloudFormation pushes the artifact to the staging account. Output: staging endpoint health.
- Manual Approval — SNS notifies the on-call AM, who clicks approve in the console after smoke-testing staging. The approval is the gate; without it, prod stage does not run.
- Deploy-Prod — CodeDeploy blue/green to production, with rollback on alarm. Output: prod endpoint health, CloudWatch alarms armed.
- Post-deploy validation — final CodeBuild action that runs synthetic transactions against prod. If any synthetic fails within the validation window, CodeDeploy auto-rolls back the blue/green cutover.
The order matters. Source then build then test gives you fast failure — a syntax error never reaches the deploy stage. Deploy-staging before manual approval means the approver sees a real staging endpoint, not a build report. Manual approval before prod is the safety bar the auditor will look for. Post-deploy validation after the cutover catches the failures that only show up under prod traffic.
How to verify
The pipeline definition below is CloudFormation. Drop it into a template, deploy it, and the entire pipeline exists in 5 minutes. The stages reference CodeBuild projects and CodeDeploy applications that must exist separately — the stanzas to create those are below the pipeline.
AppPipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub ${AppName}-pipeline
RoleArn: !GetAtt PipelineRole.Arn
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
EncryptionKey:
Id: !GetAtt ArtifactKey.Arn
Type: KMS
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeStarSourceConnection
Version: '1'
Configuration:
ConnectionArn: !Ref GitHubConnection
FullRepositoryId: 'stackharbor/app'
BranchName: main
DetectChanges: true
OutputArtifacts:
- Name: SourceOutput
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: '1'
Configuration:
ProjectName: !Ref BuildProject
InputArtifacts:
- Name: SourceOutput
OutputArtifacts:
- Name: BuildOutput
- Name: Test
Actions:
- Name: IntegrationTests
RunOrder: 1
ActionTypeId: { Category: Test, Owner: AWS, Provider: CodeBuild, Version: '1' }
Configuration: { ProjectName: !Ref IntegrationTestProject }
InputArtifacts: [{ Name: BuildOutput }]
- Name: SecurityScan
RunOrder: 1
ActionTypeId: { Category: Test, Owner: AWS, Provider: CodeBuild, Version: '1' }
Configuration: { ProjectName: !Ref SecurityScanProject }
InputArtifacts: [{ Name: BuildOutput }]
- Name: InfraPlan
RunOrder: 1
ActionTypeId: { Category: Test, Owner: AWS, Provider: CodeBuild, Version: '1' }
Configuration: { ProjectName: !Ref InfraPlanProject }
InputArtifacts: [{ Name: BuildOutput }]
- Name: DeployStaging
Actions:
- Name: DeployStaging
ActionTypeId: { Category: Deploy, Owner: AWS, Provider: CodeDeploy, Version: '1' }
Configuration:
ApplicationName: !Ref StagingApp
DeploymentGroupName: !Ref StagingGroup
InputArtifacts: [{ Name: BuildOutput }]
RoleArn: !GetAtt CrossAccountStagingRole.Arn
- Name: Approval
Actions:
- Name: ManualApproval
ActionTypeId: { Category: Approval, Owner: AWS, Provider: Manual, Version: '1' }
Configuration:
NotificationArn: !Ref ApprovalTopic
CustomData: 'Verify staging endpoint, then approve to deploy to prod.'
ExternalEntityLink: !Sub 'https://staging.${DomainName}/healthz'
- Name: DeployProd
Actions:
- Name: DeployProd
ActionTypeId: { Category: Deploy, Owner: AWS, Provider: CodeDeploy, Version: '1' }
Configuration:
ApplicationName: !Ref ProdApp
DeploymentGroupName: !Ref ProdGroup
InputArtifacts: [{ Name: BuildOutput }]
RoleArn: !GetAtt CrossAccountProdRole.Arn
- Name: PostDeployValidation
Actions:
- Name: Synthetics
ActionTypeId: { Category: Test, Owner: AWS, Provider: CodeBuild, Version: '1' }
Configuration: { ProjectName: !Ref ProdSyntheticsProject }
InputArtifacts: [{ Name: BuildOutput }]
The deployment groups (ProdGroup) reference a CodeDeploy application with DeploymentStyle: BLUE_GREEN and AlarmConfiguration pointing at the CloudWatch alarms that trigger rollback. The blue/green keeps the previous task set running until the new one passes traffic shifting, and the alarm-driven auto-rollback is what makes the synthetics gate trustworthy.
The fix
Five rules we apply to every SaaS pipeline before declaring it production-ready:
One artifact, many stages. The build artifact produced in the Build stage flows through staging and prod unchanged. Re-building between staging and prod is the most common way to ship “tested staging, untested prod” — and the most common way to fail a post-cutover validation that should have been impossible to fail.
Parallel tests, serial gates. Integration, security, and infra-plan run in parallel inside the Test stage (RunOrder: 1). Manual approval and prod deploy are serial — they cannot start until the previous stage’s RunOrder is satisfied. Mixing the two costs nothing in CloudFormation and saves minutes per pipeline run.
Manual approval is the gate, not the documentation. The approval action above has an ExternalEntityLink to the staging healthz endpoint — that link is what the approver clicks before approving. The NotificationArn is the SNS topic that pages the on-call AM. Configure both. A manual approval without notification is a pipeline that hangs silently for 7 days.
Cross-account deploy via RoleArn, not access keys. The RoleArn on the deploy actions points at a role in the target account that trusts the pipeline account. This is the IAM-native pattern CodePipeline does better than most CI tools — there are no long-lived credentials anywhere in the pipeline.
Post-deploy validation is not optional. The PostDeployValidation stage runs synthetics against prod. If it fails, CodeDeploy’s AlarmConfiguration rolls back the blue/green automatically. Without this stage, a deploy that ships a 500-error endpoint stays live until the on-call notices the alarm — minutes you do not have on a paid SaaS.
The full template is the starting point. The work after that is project-specific: which environment variables, which CodeBuild image, which test framework. The pipeline shape stays the same.
Stack Harbor builds and operates production CodePipeline workflows as part of Managed AWS onboarding and the ongoing Code Deploy Pipeline operating layer. If your release path is still SSH + a Slack message, start with an infrastructure consult.