Skip to content

End-to-end CodePipeline for a SaaS — the template that actually ships to prod

A full SaaS pipeline reference: Source, Build, parallel tests, staging deploy, manual approval, prod blue/green, post-deploy validation. The CloudFormation stanza for each stage, not the marketing diagram.

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:

  1. Source — pulls the commit from CodeCommit or GitHub via CodeStar Connections. Output: a single artifact zipped from the repo.
  2. 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.
  3. Test — three parallel CodeBuild actions: integration tests against an ephemeral environment, security scan (Snyk/Trivy/AWS Inspector), and infrastructure plan (terraform plan or cdk diff). All three must succeed.
  4. Deploy-Staging — CodeDeploy or CloudFormation pushes the artifact to the staging account. Output: staging endpoint health.
  5. 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.
  6. Deploy-Prod — CodeDeploy blue/green to production, with rollback on alarm. Output: prod endpoint health, CloudWatch alarms armed.
  7. 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.