← Back to Blog

Serverless CI/CD Pipelines: From Push to Production in Minutes

Dat NguyenJuly 20257 min readDevOps

A hands-on guide to building zero-maintenance CI/CD pipelines for serverless applications using AWS CDK, CodePipeline, and GitHub Actions.

AWSCI/CDServerlessGitHub ActionsCDKDevOps

The Problem with Traditional CI/CD

Managing Jenkins servers, patching build agents, scaling runners — CI/CD infrastructure often becomes its own project. For serverless applications, this irony is hard to ignore: your app scales to zero, but your build system runs 24/7.

Two Approaches

I've used two patterns successfully:

  1. GitHub Actions + Serverless Framework — great for small teams, fast to set up
  2. AWS CodePipeline + CDK Pipelines — better for organizations that want everything in AWS

Let's look at both.

Approach 1: GitHub Actions

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci
      - run: npm test
      - run: npm run lint

      - name: Deploy to AWS
        run: npx serverless deploy --stage production
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: ap-southeast-1

Simple, readable, and runs in under 3 minutes for most Lambda projects. The downside: your AWS credentials live in GitHub Secrets.

Better: Use OIDC federation to avoid long-lived credentials:

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: ap-southeast-1

Approach 2: CDK Pipelines

For teams that want the pipeline defined as infrastructure-as-code alongside the application:

import { CodePipeline, ShellStep, CodePipelineSource } from "aws-cdk-lib/pipelines";

const pipeline = new CodePipeline(this, "Pipeline", {
  synth: new ShellStep("Synth", {
    input: CodePipelineSource.gitHub("datj9/my-service", "main"),
    commands: [
      "npm ci",
      "npm test",
      "npx cdk synth",
    ],
  }),
});

pipeline.addStage(new StagingStage(this, "Staging"));
pipeline.addStage(new ProductionStage(this, "Production"), {
  pre: [new ManualApprovalStep("PromoteToProduction")],
});

This creates a self-mutating pipeline: if you change the pipeline definition, it updates itself on the next run.

Testing Serverless Locally

Before pushing, test locally with:

# Unit tests with Jest
npm test

# Integration tests with LocalStack
docker-compose up -d localstack
npm run test:integration

# Invoke a function locally
serverless invoke local -f processOrder --path events/order.json

Environment Promotion Strategy

feature branch → PR → main → Staging (auto) → Production (manual approval)
  • Feature branches get ephemeral stacks: serverless deploy --stage feat-xyz
  • Staging is deployed automatically on merge to main
  • Production requires manual approval (a Slack notification with an approve link)

Secrets Management

Never hardcode secrets. Use SSM Parameter Store or Secrets Manager:

const dbPassword = ssm.StringParameter.fromSecureStringParameterAttributes(
  this, "DbPassword", {
    parameterName: "/myapp/production/db-password",
  }
);

Conclusion

Serverless CI/CD should be as hands-off as the applications it deploys. GitHub Actions gets you running in 15 minutes. CDK Pipelines gives you a fully managed, self-mutating pipeline defined alongside your infrastructure. Pick the one that matches your team's complexity.