./scripts/build_layer.shThis builds an Amazon Linux 2023 container for the host architecture, installs AWS CLI v2 and jq, and stages the layer contents under layer/opt. Non-host builds are staged under layer/<arch>/opt.
To build a specific architecture or both:
ARCH=arm64 ./scripts/build_layer.sh
ARCH=amd64 ./scripts/build_layer.sh
ARCH=all ./scripts/build_layer.shCross-architecture builds require Docker buildx with QEMU/binfmt configured so the non-native image can be built locally.
The Docker build uses curl-minimal to keep dependencies small. If you need full curl features, switch the Dockerfile to curl.
To pin the AWS CLI v2 version bundled into the layer, set AWSCLI_VERSION (for example, AWSCLI_VERSION=2.32.30 ./scripts/build_layer.sh).
This uses the versioned AWS CLI installer URL and ensures the build is consistent across architectures.
./scripts/package_layer.shThis produces dist/lambda-shell-runtime-<arch>.zip with bootstrap, bin/, aws-cli/, and lib/ at the zip
root, plus a versioned artifact named dist/lambda-shell-runtime-<arch>-<aws-cli-version>.zip. The script also
generates dist/template-*.yaml with SemanticVersion set to the bundled AWS CLI v2 version and ContentUri
pointing at the packaged zips.
The source template*.yaml files remain static; publishing uses the generated templates.
To package both architectures:
ARCH=all ./scripts/package_layer.shTemplate generation overrides:
TEMPLATE_VERSION(override theSemanticVersionwritten intodist/template-*.yaml; defaults to the bundled AWS CLI v2 version)TEMPLATE_OUTPUT_DIR(output directory for generated templates; default:dist)
./scripts/test-smoke.shThe smoke test:
- checks
aws --versionandjq --version - runs the
bootstrapagainst a local mock Runtime API and the example handler
Run ShellSpec suites via make:
make test-smoke
make test-unit
make test-int
make testmake test runs the smoke, unit, and integration specs together.
All shell scripts are written for POSIX sh and should pass shellcheck.
shellcheck runtime/bootstrap scripts/*.sh examples/function.shProject defaults live in scripts/aws_env.sh. It pins the AWS region to us-east-1 and sets default names
for the S3 bucket, setup stack, SAR applications, and the S3 prefix used for SAR artifacts. These defaults
are used by make aws-check, make aws-setup, and make release.
./scripts/package_layer.sh renders dist/template-*.yaml with SAR application names aligned with these defaults; rerun it after changing the app name settings.
Set ENV=dev to switch to the dev defaults (the *_DEV values).
Override any of the defaults by exporting:
ENV(prodordev, default:prod)LSR_AWS_REGIONLSR_BUCKET_NAME(prod bucket)LSR_BUCKET_NAME_DEV(dev bucket; defaults to the prod bucket)LSR_STACK_NAME(prod setup stack)LSR_STACK_NAME_DEV(dev setup stack; defaults to the prod stack)LSR_SAR_APP_BASE(prod SAR app base)LSR_SAR_APP_BASE_DEV(dev SAR app base)LSR_SAR_VERSION_DEV(dev SAR version, default0.0.0)LSR_SAR_APP_NAME_ARM64/LSR_SAR_APP_NAME_AMD64(override derived names)LSR_LAYER_NAME_ARM64/LSR_LAYER_NAME_AMD64(override layer names)LSR_SAR_PUBLIC(set to0to keep SAR apps private; defaults to1in prod,0in dev)LSR_S3_PREFIXS3_BUCKET(optional; overrides the bucket used for packaging)
Use the make release target locally or the GitHub Actions workflow .github/workflows/release.yml (it calls the same target).
S3_BUCKET=your-bucket make releaseIt:
- builds and packages both architectures
- generates
dist/template-*.yamlwithSemanticVersionset to the bundled AWS CLI v2 version - checks for an existing Git tag
- publishes the SAR applications with
sam package/sam publish - renders the wrapper template with the arm64/amd64 ApplicationIds and publishes the wrapper application
- uploads SAR artifacts under
S3_PREFIX/<version>/<arch>
To check whether a release is needed without building anything, run:
make check-releaseThis checks the latest AWS CLI v2 version (via the AWS CLI GitHub tags) against existing Git tags, and verifies the versioned AWS CLI downloads are available for both architectures.
To delete the current GitHub release and tag (so you can re-run make release), run:
make delete-releaseNote: SAR application versions are immutable. Deleting a GitHub release/tag does not remove the SAR version that was already published.
For fast iteration without touching the stable SAR apps, use the dev-only app (amd64 only). When ENV=dev and ARCH is not set, the SAR scripts default to amd64.
ENV=dev make publish-sar ARCH=amd64Aliases:
make publish-dev-amd64
Defaults:
- SAR app base:
lambda-shell-runtime-dev(override withLSR_SAR_APP_BASE_DEV) - SAR app name:
<base>-amd64(override withLSR_SAR_APP_NAME_AMD64) - Layer name: defaults to the SAR app name (override with
LSR_LAYER_NAME_AMD64) - S3 bucket:
lambda-shell-runtime(defaults to the prod bucket; override withLSR_BUCKET_NAME_DEVorS3_BUCKET) - Dev version:
0.0.0(override withLSR_SAR_VERSION_DEV) - S3 prefix:
LSR_S3_PREFIX/S3_PREFIX(dev publishes underS3_PREFIX/0.0.0/<arch>by default)
This publishes only the dev amd64 SAR application and skips the wrapper/stable apps. Dev arm64 is intentionally not published or checked.
To delete the dev SAR app (so you can republish the same version), run:
ENV=dev make delete-sar ARCH=amd64Aliases:
make delete-release-amd64make delete-dev-amd64
To deploy the dev SAR app into your account (CloudFormation stacks), run:
ENV=dev make deploy-sar ARCH=amd64Aliases:
make deploy-dev-amd64
Local requirements:
- Docker with buildx/QEMU (for cross-arch)
sam,gh, andawsCLIs installed (gh auth loginorGH_TOKENrequired)S3_BUCKETset in the environment (or viamake release S3_BUCKET=...)S3_PREFIX(optional; defaults tosar, used as the base prefix for SAR artifacts)
Repo setup required for the workflow:
AWS_ROLE_ARNsecret for OIDCAWS_REGIONandS3_BUCKETrepository variables- allow GitHub Actions to push to
mainif branch protection is enabled
To run in GitHub: Actions -> Release -> Run workflow.
Manual fallback:
- Ensure
ARCH=all ./scripts/package_layer.shhas been run so the versioned artifacts are created. - Tag the release in Git. Use the AWS CLI version as the tag to match the generated templates'
SemanticVersion.
If you use make release (locally or via the workflow), SAR publishing is handled there. For manual publishing, prefer the make targets (they call sam package/sam publish and use S3_PREFIX/<version>/<arch>):
make publish-arm64
make publish-amd64These expect dist/lambda-shell-runtime-<arch>.zip to exist (run make package-<arch> first).
To publish all three SAR applications (arm64, amd64, wrapper) in order:
make publish-allmake publish-wrapper and make publish-all generate the wrapper template with the correct ApplicationIds automatically.
By default, prod publishing makes the SAR applications public (per-arch + wrapper). Set LSR_SAR_PUBLIC=0 to keep them private.
To publish the wrapper application, run:
make publish-wrapperIf you want the raw SAM commands for the per-arch apps:
- Build and package the layer (this generates
dist/template-*.yamlwithSemanticVersionset to the bundled AWS CLI version):
./scripts/build_layer.sh
./scripts/package_layer.sh- Set
S3_BUCKETto an S3 bucket in your account and package each template (use a versioned prefix likesar/<version>/<arch>to group artifacts):
sam package \
--template-file dist/template-arm64.yaml \
--s3-bucket "$S3_BUCKET" \
--s3-prefix "sar/<version>/arm64" \
--output-template-file dist/packaged-arm64.yaml
sam package \
--template-file dist/template-amd64.yaml \
--s3-bucket "$S3_BUCKET" \
--s3-prefix "sar/<version>/amd64" \
--output-template-file dist/packaged-amd64.yaml- Publish to SAR:
sam publish --template dist/packaged-arm64.yaml
sam publish --template dist/packaged-amd64.yaml- Publish the wrapper application (it is rendered with the architecture application IDs):
make publish-wrapperUse make aws-setup to bootstrap the S3 bucket and create the SAR applications if they do not exist.
CloudFormation cannot create SAR applications directly, so the target creates the bucket via template/aws-setup.yaml
and then runs sam publish to create the first SAR version if needed.
make aws-setupTo apply the bucket policy without SAR publishing, use:
ENV=dev SKIP_SAR_PUBLISH=1 make aws-setupAlias:
make aws-setup-dev
Options:
BUCKET_NAME(default:lambda-shell-runtime)STACK_NAME(default:lambda-shell-runtime-setup)SAR_APP_NAME_BASE(default:lambda-shell-runtime)SAR_APP_NAME_ARM64(default:lambda-shell-runtime-arm64)SAR_APP_NAME_AMD64(default:lambda-shell-runtime-amd64)S3_BUCKET(optional; defaults toBUCKET_NAMEfor packaging)S3_PREFIX(optional; defaults tosar)
Dev options:
LSR_BUCKET_NAME_DEV(default:lambda-shell-runtime)LSR_STACK_NAME_DEV(default:lambda-shell-runtime-setup)LSR_SAR_APP_BASE_DEV(default:lambda-shell-runtime-dev)LSR_SAR_VERSION_DEV(default:0.0.0)
With the defaults above, ENV=dev reuses the prod bucket and setup stack; only the SAR app names and version differ.
Behavior:
- If the bucket already exists and is accessible, the stack skips bucket creation to avoid failure.
- If the stack created the bucket, deleting the stack will fail when the bucket is not empty.
- If any SAR application is missing,
make aws-setuprunsmake package-alland publishes the first version (arm64, amd64, and the wrapper). - The bucket policy grants SAR read access only to the configured
S3_PREFIX. - The lifecycle rule transitions objects under
S3_PREFIX/to STANDARD_IA after 30 days and GLACIER_IR after 90 days.
The manual workflow .github/workflows/aws-check.yml validates GitHub Actions access to AWS for both dev and prod. It runs make aws-check, which you can also execute locally. Set ENV and S3_BUCKET to target a specific environment, and use AWS_CHECK_ARCHES to limit checks to specific architectures (dev defaults to amd64 only).
To remove AdministratorAccess from the GitHub Actions role, attach a least-privilege policy that covers:
make publish-sarandmake deploy-sar(dev CI)make release(prod release)make aws-check(permission validation)
Create an IAM policy in the AWS console (IAM → Policies → Create policy → JSON) using the template below.
Replace <ACCOUNT_ID>, <REGION>, and <S3_BUCKET> to match your setup.
serverlessrepo:CreateApplication does not support resource-level scoping, so the SAR block uses Resource: "*", with region restriction applied.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "StsIdentity",
"Effect": "Allow",
"Action": "sts:GetCallerIdentity",
"Resource": "*"
},
{
"Sid": "CloudFormationDeploy",
"Effect": "Allow",
"Action": [
"cloudformation:CreateChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:DeleteChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackEvents",
"cloudformation:DescribeStackResources",
"cloudformation:GetTemplate",
"cloudformation:GetTemplateSummary",
"cloudformation:ListStacks",
"cloudformation:ValidateTemplate",
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"cloudformation:DeleteStack"
],
"Resource": "*"
},
{
"Sid": "LambdaLayers",
"Effect": "Allow",
"Action": [
"lambda:PublishLayerVersion",
"lambda:DeleteLayerVersion",
"lambda:GetLayerVersion"
],
"Resource": "arn:aws:lambda:<REGION>:<ACCOUNT_ID>:layer:lambda-shell-runtime*"
},
{
"Sid": "LambdaLayerRead",
"Effect": "Allow",
"Action": "lambda:ListLayers",
"Resource": "*"
},
{
"Sid": "ServerlessRepo",
"Effect": "Allow",
"Action": [
"serverlessrepo:ListApplications",
"serverlessrepo:CreateApplication",
"serverlessrepo:GetApplication",
"serverlessrepo:UpdateApplication",
"serverlessrepo:PutApplicationPolicy",
"serverlessrepo:CreateApplicationVersion",
"serverlessrepo:CreateCloudFormationTemplate",
"serverlessrepo:GetCloudFormationTemplate",
"serverlessrepo:DeleteApplication"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "<REGION>"
}
}
},
{
"Sid": "S3PackagingBucketsList",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads"
],
"Resource": "arn:aws:s3:::<S3_BUCKET>"
},
{
"Sid": "S3PackagingBucketsObjects",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::<S3_BUCKET>/*"
},
{
"Sid": "SarChangesetRead",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::awsserverlessrepo-changesets-*/*"
}
]
}After creating the policy:
- IAM → Roles →
GitHubActionsLambdaShellRuntime→ Attach policies → attach the new policy. - Remove
AdministratorAccessfrom the role. - If you use permission boundaries or SCPs, ensure they do not deny
s3:GetObjectforawsserverlessrepo-changesets-*. - In GitHub repo variables, set
AWS_REGIONandS3_BUCKET.
- Create the GitHub OIDC provider in AWS (if not already present):
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
- Provider URL:
- Create an IAM role for GitHub Actions with:
- Trust policy allowing
sts:AssumeRoleWithWebIdentityfrom your repo and branch. - For initial setup, attach
AdministratorAccess. You can reduce permissions later.
- Trust policy allowing
Example trust policy (replace account ID, org, repo, and branch):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<account-id>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:ORG/REPO:ref:refs/heads/main"
}
}
}
]
}- Add a repository secret
AWS_ROLE_ARNwith the IAM role ARN. - Add a repository variable
AWS_REGION(for example,us-east-1).
In GitHub, open the Actions tab, select AWS Connectivity, and click Run workflow. The job runs aws sts get-caller-identity and lists a few Lambda layers to confirm connectivity.