Building AWS SAM Runtime Images
This page provides a Harness CD pipeline to help you build your own Docker images for the AWS SAM CLI.
The purpose of this pipeline is to give you flexibility—so you can adopt newer AWS Lambda runtimes or tailor the image to your specific serverless application needs.
What This Pipeline Does
This pipeline automates building AWS SAM images for different programming languages using Harness. It enables you to keep up with the latest SAM versions and apply customizations as required for your projects.
You can find the full pipeline YAML in the Pipeline YAML section below.
Understanding SAM Runtimes
SAM runtimes refer to the programming language environments that AWS Serverless Application Model (SAM) supports for Lambda function development. Each runtime provides the necessary language-specific libraries, tools, and dependencies needed to build, test, and deploy serverless applications.
Common SAM runtimes include:
- Node.js: Versions like nodejs18.x, nodejs20.x
- Python: Versions like python3.9, python3.10, python3.11
- Java: Versions like java11, java17
- Ruby: Versions like ruby3.2
- Go: Versions like go1.x
When you build your own image using the Harness pipeline, you're combining the Harness SAM base image (which provides the integration with Harness CD) with a specific SAM runtime image from AWS. This allows you to deploy serverless applications written in your preferred programming language while leveraging Harness deployment capabilities.
Key Components and pre-requisites
This pipeline helps you build custom AWS SAM images using Harness, integrating the Harness SAM image with supported AWS Lambda runtimes. Key details about the pipeline include:
- 
Deployment Stage with Kubernetes Infrastructure - Uses a Deployment stage configured to run on Kubernetes.
 
- 
Privileged Mode and Kubernetes Cluster Setup - The pipeline requires privileged mode enabled on the Kubernetes step group to support Docker-in-Docker (DinD) for building and pushing images.
- This mode grants necessary permissions to install and run Docker CLI commands and access the Docker daemon inside pipeline containers.
- When using managed Kubernetes services like GKE, do not use Autopilot clusters, which restrict privileged containers.
- Instead, use standard clusters with node pools configured to permit privileged pods.
- Connect your Kubernetes cluster to Harness via a Kubernetes Cluster connector with appropriate permissions.
 
- 
Use of Official AWS SAM Images - Pulls SAM base images exclusively from the AWS ECR public gallery for compatibility.
 
- 
Automatic Extraction of Runtime and Version - The pipeline extracts runtime and version details directly from the SAM base image name.
 
- 
Final Image Naming Convention - The final built images follow the pattern:
 aws-sam-plugin:{VERSION}-{SAM_RUNTIME}-{SAM_VERSION}-linux-amd64
 Example:aws-sam-plugin:1.1.2-nodejs18.x-1.143.0-linux-amd64
 
- The final built images follow the pattern:
Pipeline Runner Privileged Mode Requirement
Certain steps in the pipeline require the Kubernetes pod to run in privileged mode. This is necessary for starting Docker daemons (DinD), building container images inside pipeline steps, and granting the permissions Docker needs at runtime.
Why privileged mode is required:
- Enables Docker-in-Docker (DinD) support for building and pushing images.
- Allows installation and execution of docker CLI and manipulation of containers within the build step.
- Required for root access and mounting Docker volumes.
To enable privileged execution, set privileged: true in the step group or step-level security context. Example:
stepGroup:
  privileged: true
  name: k8s-step-group
  sharedPaths:
    - /var/run
    - /var/lib/docker
For individual steps:
step:
  name: dinD
  privileged: true
  ...
Without this setting, Docker builds and image pushes may fail due to insufficient permissions inside the container.
Quick Start
- Copy the provided pipeline YAML and paste it in your Harness Project.
- Add an empty/do-nothing service to the pipeline.
- Add a Kubernetes environment to the pipeline.
- In the Execution section, enable container-based execution in the step group. Add the Kubernetes cluster connector inside the container step group. Save the pipeline.
- Click Run Pipeline.
- Enter the required parameters:
- VERSION: Version number for your plugin (e.g., 1.1.2). With each new code change, a new tag and Docker image are published, letting users access specific plugin versions. . You can find the Harness base image on Harness DockerHub
- SAM_BASE_IMAGE: SAM base image from AWS ECR Gallery (e.g., public.ecr.aws/sam/build-nodejs18.x:1.143.0-20250502200316-x86_64).
 
- VERSION: Version number for your plugin (e.g., 
Base Image Requirements
Only official AWS SAM build images from the AWS ECR Public Gallery are supported.
- Only use SAM base images from: AWS ECR Gallery - SAM
- Only x86_64architecture images are supported
- Using different base images may cause library dependency issues
- Non-standard base images may cause the plugin to not function as required
SAM Base Image Format
The pipeline supports only full formats for the SAM base image:
Full Format: public.ecr.aws/sam/build-nodejs18.x:1.143.0-20250502200316-x86_64
Image Configuration
The final image follows this naming pattern:
aws-sam-plugin:${VERSION}-${SAM_RUNTIME}-${SAM_VERSION}-linux-amd64
Example:
aws-sam-plugin:1.1.2-nodejs18.x-1.138.0-linux-amd64
Where:
- VERSION: Harness base image (e.g.,- 1.1.2)
- SAM_RUNTIME: Runtime extracted from SAM base image (e.g.,- nodejs18.x)
- SAM_VERSION: Version extracted from SAM base image (e.g.,- 1.143.0)
Variables Used in Pipeline
These variables are actively used in the pipeline for building and pushing the image that you need to configure:
Pipeline variables: - TARGET_REPO, DOCKER_USERNAME, and DOCKER_PASSWORD are set once as pipeline-level variables.
| Variable | Description | Example | Required | 
|---|---|---|---|
| TARGET_REPO | Target Docker repository | your_account/aws-sam-plugin | Yes | 
| DOCKER_USERNAME | Docker registry username | your_dockerhub_username | Yes | 
| DOCKER_PASSWORD | Docker registry password/token | your_dockerhub_pat | Yes | 
Runtime inputs:
| Variable | Description | Example | Required | 
|---|---|---|---|
| VERSION | Harness base image-version tag | 1.1.2 | Yes | 
| Harness_BASE_IMAGE | Reference to your built base image | harness/aws-sam-plugin:1.1.2-beta-base-image | Yes | 
| SAM_BASE_IMAGE | AWS SAM base image from ECR | public.ecr.aws/sam/build-python3.12:1.143.0-20250822194415-x86_64 | Yes | 
Pipeline YAML
This is the YAML for the AWS CDK image build pipeline. You can copy and paste it into your Harness Project.
This is how the stage would look in the UI:

Pipeline YAML
Parameters to change after you copy the pipeline YAML and paste it in your Harness Project:
- projectIdentifier,- orgIdentifier,- environmentRef,- infrastructureDefinitions,- connectorRef- docker-connector,- connectorRef- k8s-connector.
pipeline:
  name: sam-image-build
  identifier: samimagebuild
  projectIdentifier: your_project
  orgIdentifier: default
  tags: {}
  stages:
    - stage:
        name: combineImages
        identifier: combineImages
        description: Combine Harness base image with SAM base image and push to Docker
        type: Deployment
        spec:
          deploymentType: Kubernetes
          service:
            serviceRef: service
          environment:
            environmentRef: k8s
            deployToAll: false
            infrastructureDefinitions:
              - identifier: your_k8s_infra
          execution:
            steps:
              - stepGroup:
                  privileged: true
                  name: k8s-step-group
                  identifier: k8sstepgroup
                  sharedPaths:
                    - /var/run
                    - /var/lib/docker
                  steps:
                    - step:
                        type: Background
                        name: dinD
                        identifier: Background
                        spec:
                          connectorRef: account.dockerhub
                          image: docker:24-dind
                          shell: Sh
                          privileged: true
                    - step:
                        identifier: generateTimestamp
                        type: Run
                        name: sam-prepare-build
                        spec:
                          connectorRef: account.dockerhub
                          image: docker:24
                          shell: Sh
                          command: |-
                            #!/bin/bash
                            set -e
                            echo "Waiting for Docker daemon"
                            ls -l /var/run/docker.sock
                            until docker info; do sleep 1; done
                            export DEBIAN_FRONTEND=noninteractive
                            export TZ=UTC
                            VERSION="${VERSION:-<+pipeline.variables.VERSION>}"
                            HARNESS_BASE_IMAGE="${HARNESS_BASE_IMAGE:-<+pipeline.variables.HARNESS_BASE_IMAGE>}"
                            SAM_BASE_IMAGE="${SAM_BASE_IMAGE:-<+pipeline.variables.SAM_BASE_IMAGE>}"
                            TARGET_REPO="${TARGET_REPO:-<+pipeline.variables.TARGET_REPO>}"
                            DOCKER_USERNAME="<+pipeline.variables.DOCKER_USERNAME>"
                            DOCKER_PASSWORD="<+pipeline.variables.DOCKER_PASSWORD>"
                            TIMESTAMP=$(date -u +"%Y%m%d%H%M%S")
                            SAM_RUNTIME=$(echo "${SAM_BASE_IMAGE}" | sed 's|.*build-\([^:]*\):.*|\1|')
                            SAM_VERSION=$(echo "${SAM_BASE_IMAGE}" | sed 's|.*:\([0-9]*\.[0-9]*\.[0-9]*\).*|\1|')
                            if [ -z "$SAM_RUNTIME" ] || [ -z "$SAM_VERSION" ]; then
                                echo "ERROR: Could not parse SAM base image format"
                                exit 1
                            fi
                            FINAL_IMAGE="${TARGET_REPO}:${VERSION}-${SAM_RUNTIME}-${SAM_VERSION}-linux-amd64-${TIMESTAMP}"
                            if ! command -v docker >/dev/null 2>&1; then
                              apt-get update && apt-get install -y docker.io
                            fi
                            echo "=== Waiting for Docker daemon ==="
                            until docker info >/dev/null 2>&1; do
                              sleep 2
                            done
                            echo "=== Docker login ==="
                            echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
                            echo "=== Creating build context ==="
                            mkdir -p /tmp/sam-build
                            cd /tmp/sam-build
                            cat > Dockerfile << EOF
                            FROM ${SAM_BASE_IMAGE}
                            ENV HARNESS_GO_PLUGIN_VERSION=v0.4.5
                            ENV DOCKER_VERSION=26.0.1
                            ENV INSTALL_GO_TEMPLATE_BINARY=true
                            ENV UNIFIED_PIPELINE=false
                            # Install Docker CLI
                            # RUN apt-get update && \\
                            #     apt-get install -y docker.io && \\
                            #     rm -rf /var/lib/apt/lists/*
                            RUN if command -v apt-get >/dev/null 2>&1; then \
                                  apt-get update && apt-get install -y docker.io && rm -rf /var/lib/apt/lists/*; \
                                elif command -v yum >/dev/null 2>&1; then \
                                  yum install -y yum-utils && \
                                  echo "[docker-ce]" > /etc/yum.repos.d/docker-ce.repo && \
                                  echo "name=Docker CE Repository" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "baseurl=https://download.docker.com/linux/centos/7/x86_64/stable" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "enabled=1" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "gpgcheck=1" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "gpgkey=https://download.docker.com/linux/centos/gpg" >> /etc/yum.repos.d/docker-ce.repo && \
                                  yum install -y docker-ce-cli; \
                                elif command -v microdnf >/dev/null 2>&1; then \
                                  echo "[docker-ce]" > /etc/yum.repos.d/docker-ce.repo && \
                                  echo "name=Docker CE Repository" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "baseurl=https://download.docker.com/linux/centos/7/x86_64/stable" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "enabled=1" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "gpgcheck=1" >> /etc/yum.repos.d/docker-ce.repo && \
                                  echo "gpgkey=https://download.docker.com/linux/centos/gpg" >> /etc/yum.repos.d/docker-ce.repo && \
                                  microdnf install -y docker-ce-cli; \
                                elif command -v apk >/dev/null 2>&1; then \
                                  apk add --no-cache docker-cli; \
                                else \
                                  echo "Package manager not found, skipping docker CLI install"; \
                                fi
                            RUN mkdir -m 777 -p /opt/harness/bin/ && \\
                                mkdir -m 777 -p /opt/harness/scripts/ && \\
                                mkdir -m 777 -p /opt/harness/client-tools/
                            COPY --from=${HARNESS_BASE_IMAGE} /opt/harness/bin/harness-sam-plugin /opt/harness/bin/harness-sam-plugin
                            COPY --from=${HARNESS_BASE_IMAGE} /opt/harness/scripts/ /opt/harness/scripts/
                            RUN chmod +x /opt/harness/bin/harness-sam-plugin && \\
                                chmod +x /opt/harness/scripts/sam-plugin.sh
                            RUN if [ "\$INSTALL_GO_TEMPLATE_BINARY" = "true" ] && [ "\$UNIFIED_PIPELINE" != "true" ]; then \\
                                curl -s -L -o /opt/harness/client-tools/go-template https://app.harness.io/public/shared/tools/go-template/release/\${HARNESS_GO_PLUGIN_VERSION}/bin/linux/amd64/go-template && \\
                                chmod +x /opt/harness/client-tools/go-template; \\
                              else \\
                                echo "Skipping go-template binary."; \\
                              fi
                            ENTRYPOINT ["/opt/harness/scripts/sam-plugin.sh"]
                            EOF
                            echo "=== Building and pushing image ==="
                            docker build -t ${FINAL_IMAGE} .
                            docker push ${FINAL_IMAGE}
                            echo "✓ SUCCESS: ${FINAL_IMAGE}"
                          envVariables:
                            VERSION: <+pipeline.variables.VERSION>
                            SAM_BASE_IMAGE: <+pipeline.variables.SAM_BASE_IMAGE>
                            SOURCE_REGISTRY: <+pipeline.variables.SOURCE_REGISTRY>
                            TARGET_REGISTRY: <+pipeline.variables.TARGET_REGISTRY>
                          outputVariables:
                            - name: TIMESTAMP
                              type: String
                              value: TIMESTAMP
                            - name: VCS_REF
                              type: String
                              value: VCS_REF
                            - name: SAM_RUNTIME
                              type: String
                              value: SAM_RUNTIME
                            - name: SAM_VERSION
                              type: String
                              value: SAM_VERSION
                          resources:
                            limits:
                              memory: 4Gi
                              cpu: 2000m
                        description: SAM Prepare Build
                        timeout: 30m
                    - step:
                        identifier: buildAndPushFinal
                        type: Run
                        name: buildAndPushImage
                        spec:
                          connectorRef: account.dockerhub
                          image: ubuntu:20.04
                          shell: Bash
                          command: |-
                            #!/bin/bash
                            set -e
                            # Set non-interactive mode
                            export DEBIAN_FRONTEND=noninteractive
                            export TZ=UTC
                            # Set non-interactive mode
                            export DEBIAN_FRONTEND=noninteractive
                            export TZ=UTC
                            # Define variables from pipeline variables
                            VERSION="<+pipeline.variables.VERSION>"
                            HARNESS_BASE_IMAGE="<+pipeline.variables.HARNESS_BASE_IMAGE>"
                            SAM_BASE_IMAGE="<+pipeline.variables.SAM_BASE_IMAGE>"
                            TIMESTAMP="<+pipeline.variables.TIMESTAMP>"
                            # Print all variables for debugging
                            echo "VERSION: $VERSION"
                            echo "HARNESS_BASE_IMAGE: $HARNESS_BASE_IMAGE"
                            echo "SAM_BASE_IMAGE: $SAM_BASE_IMAGE"
                            echo "TIMESTAMP: $TIMESTAMP"
                            apt-get update && apt-get install -y docker.io
                            # Start Docker daemon
                            dockerd &
                            sleep 10
                            # Wait for Docker
                            until docker info >/dev/null 2>&1; do sleep 1; done
                            # Login to Docker
                            echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
                            # Go to build context (created by Step 1)
                            cd /harness/sam-build
                            # Build and push (exactly like your local script)
                            echo "=== Building and pushing image ==="
                            docker build -t ${FINAL_IMAGE} .
                            docker push ${FINAL_IMAGE}
                            echo "✓ SUCCESS: ${FINAL_IMAGE}"
                          privileged: true
                          envVariables:
                            VERSION: <+pipeline.variables.VERSION>
                            SAM_BASE_IMAGE: <+pipeline.variables.SAM_BASE_IMAGE>
                            SOURCE_REGISTRY: <+pipeline.variables.SOURCE_REGISTRY>
                            TARGET_REGISTRY: <+pipeline.variables.TARGET_REGISTRY>
                            SOURCE_REGISTRY_HOST: <+pipeline.variables.SOURCE_REGISTRY_HOST>
                            TARGET_REGISTRY_HOST: <+pipeline.variables.TARGET_REGISTRY_HOST>
                            DOCKER_USERNAME: <+pipeline.variables.DOCKER_USERNAME>
                            DOCKER_TOKEN: <+pipeline.variables.DOCKER_TOKEN>
                            TARGET_DOCKER_USERNAME: <+pipeline.variables.TARGET_DOCKER_USERNAME>
                            TARGET_DOCKER_TOKEN: <+pipeline.variables.TARGET_DOCKER_TOKEN>
                            TIMESTAMP: <+steps.generateTimestamp.output.outputVariables.TIMESTAMP>
                            VCS_REF: <+steps.generateTimestamp.output.outputVariables.VCS_REF>
                            SAM_RUNTIME: <+steps.generateTimestamp.output.outputVariables.SAM_RUNTIME>
                            SAM_VERSION: <+steps.generateTimestamp.output.outputVariables.SAM_VERSION>
                          resources:
                            limits:
                              memory: 8Gi
                              cpu: 4000m
                        timeout: 30m
                  stepGroupInfra:
                    type: KubernetesDirect
                    spec:
                      connectorRef: your_k8s_connector
            rollbackSteps: []
        tags: {}
        failureStrategies:
          - onFailure:
              errors:
                - AllErrors
              action:
                type: StageRollback
  allowStageExecutions: true
  variables:
    - name: VERSION
      type: String
      description: Plugin version (e.g., 1.1.2-beta)
      required: true
      value: <+input>.default(1.1.2)
    - name: HARNESS_BASE_IMAGE
      type: String
      description: harness base image from Pipeline 1
      required: true
      value: <+input>.default(harness/aws-sam-plugin:1.1.2-beta-base-image)
    - name: SAM_BASE_IMAGE
      type: String
      description: SAM base image (e.g., public.ecr.aws/sam/build-python3.12:1.143.0-20250822194415-x86_64)
      required: true
      value: <+input>.default(public.ecr.aws/sam/build-nodejs22.x:1.144.0-20250911030138-x86_64)
    - name: DOCKER_USERNAME
      type: String
      description: Docker Hub username
      required: false
      value: your_dockerhub_username
    - name: DOCKER_PASSWORD
      type: String
      description: Docker Hub PAT
      required: false
      value: <+secrets.getValue("dockerhub_pat")>
    - name: TARGET_REPO
      type: String
      description: Target repository
      required: false
      value: your_target_repository
    - name: TIMESTAMP
      type: String
      description: Build timestamp
      required: false
      value: <+execution.steps.k8sstepgroup.steps.sampreparebuild.output.outputVariables.TIMESTAMP>