Right, let’s talk about the moving parts of your pipeline. You’ve defined the stages, but a stage without an action is like a concert stage with no band—just a sad, empty space. Pipeline actions are where the actual work gets done, and AWS gives you two main flavors: their own native stuff and integrations with third-party tools you probably already have a love-hate relationship with.

The key thing to remember is that an action is just a plugin. It’s a little bundle of code that tells your pipeline stage, “Hey, go do this specific thing at this specific point.” This architecture is why the whole system feels so flexible and also, occasionally, a bit like herding cats.

AWS Native Actions: The Usual Suspects

These are the built-in actions for the core AWS CI/CD services. You’ll use these most of the time because, well, they’re right there and they just work.

The big ones are:

  • Source: For pulling your code from a repository (e.g., CodeStarSourceConnection for GitHub, S3 for a zip file in a bucket).
  • Build: For running your build and test suite, which is, of course, a CodeBuild action.
  • Deploy: For shipping your code to a service, like CodeDeployToECS for Fargate or CloudFormation for creating/updating infrastructure.

Here’s the kicker: while you define the pipeline in CodePipeline, the real configuration for a Build or Deploy action often lives in another service entirely (CodeBuild or CodeDeploy). The pipeline action is just a reference. This is both powerful and annoying. Powerful because you can configure a complex build in CodeBuild and reuse it across pipelines. Annoying because when you’re debugging, you now have three different AWS consoles to jump between. Pro tip: open all three in tabs and mutter to yourself for the full experience.

A realistic action definition in your CloudFormation template for a build stage looks like this:

- Name: Build
  Actions:
    - Name: Build
      ActionTypeId:
        Category: Build
        Owner: AWS
        Provider: CodeBuild
        Version: '1'
      Configuration:
        ProjectName: !Ref MyCodeBuildProject # This is the key—it points to your actual CodeBuild project
        EnvironmentVariables: !Sub |
          [
            {"name": "PIPELINE_EXECUTION_ID", "value": "#{codepipeline.PipelineExecutionId}"}
          ]
      OutputArtifacts:
        - Name: BuildOutput
      RunOrder: 1

Notice the EnvironmentVariables passing the pipeline execution ID. This is a classic pattern. You pass metadata from the pipeline context into the build environment so your build scripts can use it (e.g., to tag a Docker image with the pipeline ID). Forgetting to pass crucial context like this is a common pitfall that leads to builds that work in isolation but fail mysteriously inside the pipeline.

Third-Party Actions: Because We Don’t Live in a Vacuum

AWS is not an island, even if it sometimes tries to be. You’ve got investments in other tools, and CodePipeline knows this. The most common integrations are with GitHub (for source), Jenkins (for build), and Jira (for approvals).

The GitHub (V2) source action is what you’ll use 90% of the time. It uses a CodeStar Connection—a fancy way of saying you authorize AWS to talk to your GitHub account via OAuth. The first time you set this up, it feels a bit like magic. Then you realize you have to manage that connection in the AWS Console for each region, and the magic wears off slightly. The CloudFormation for it is verbose but necessary:

- Name: Source
  Actions:
    - Name: GitHub_Source
      ActionTypeId:
        Category: Source
        Owner: AWS
        Provider: CodeStarSourceConnection
        Version: '1'
      Configuration:
        ConnectionArn: "arn:aws:codestar-connections:us-east-1:123456789012:connection/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111" # Created separately
        FullRepositoryId: "my-org/my-repo"
        BranchName: main
        OutputArtifactFormat: CODEBUILD_CLONE_ZIP # This is the important one for CodeBuild
      OutputArtifacts:
        - Name: SourceOutput
      RunOrder: 1

Then there’s Jenkins. Sometimes you have a giant, complex, slightly fragile Jenkins setup that you’re not ready to migrate to CodeBuild. That’s okay. CodePipeline can trigger a Jenkins job as a build action and wait for it to finish. The setup involves a lot of cross-account IAM permissions and making sure your Jenkins instance is accessible to the pipeline. It’s… finicky. Honestly, it often feels like trying to get two rival superheroes to work together. It can be done, but there’s a lot of ego involved.

Jira approvals are a nifty trick. You can add an approval action that waits for a Jira ticket to transition to a specific status (e.g., “Approved”) before the pipeline continues. It’s a great way to enforce “no deployment without a ticket.” The configuration involves setting up a Jira API token in AWS Secrets Manager and crafting a JQL query. It’s powerful, but if your Jira instance is slow, your pipeline will be slow. It also introduces a hard external dependency—if Jira is down, your deployments are dead in the water. Use this pattern, but be aware of the trade-off.

The unifying concept for all these actions is the artifact. Every action consumes input artifacts and produces output artifacts. These are just zip files in an S3 bucket that CodePipeline manages for you. Your build action takes the source artifact, unzips it, builds it, and zips up the resulting compiled code or Docker image into a new output artifact. The deploy action then takes that artifact and does its thing. If you ever wonder “how does the built code get from Build to Deploy?”, the answer is always “S3.” It’s the universal courier of the AWS ecosystem.