infra/catalog-api.yaml declares the CDK 24 app's resources by hand, with a role allowing only dynamodb:Scan and Code: ../api/, a path that aws cloudformation package zips, uploads and replaces. cfn-lint 2,641 flags that path (W3002) and exits with 4, failing the job, so .cfnlintrc.yaml ignores W3002, with a comment saying why:
name: Deploy to LocalStack
on:
push:
branches: [main]
paths: ["infra/**", "api/**", ".github/workflows/deploy.yml"]
workflow_dispatch:
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-24.04
services:
localstack:
image: localstack/localstack:4.13.1
ports: ["4566:4566"]
volumes: ["/var/run/docker.sock:/var/run/docker.sock"]
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
steps:
- uses: actions/checkout@v7
- name: Lint the templates
run: |
pipx install cfn-lint==1.57.0
cfn-lint && echo "cfn-lint: no findings"
- name: Package the Lambda code
run: |
aws s3 mb s3://booknest-artifacts
aws cloudformation package --template-file infra/catalog-api.yaml \
--s3-bucket booknest-artifacts --output-template-file packaged.yaml
- name: Deploy the stack
run: |
aws cloudformation deploy --stack-name booknest-api \
--template-file packaged.yaml --capabilities CAPABILITY_IAM
- name: Smoke-test the function
run: |
aws dynamodb put-item --table-name booknest-books \
--item '{"id": {"N": "1"}, "title": {"S": "The Quiet Harbor"}}'
aws lambda invoke --function-name booknest-count-books out.json
cat out.json
- name: Remove the Lambda containers LocalStack started
if: always()
run: |
docker ps -q --filter network=${{ job.services.localstack.network }} \
--filter name=-lambda- | xargs -r docker rm -fThe runner waits for the image's own health check before the first step, and the published port puts LocalStack 63,725 at localhost:4566; the test keys are dummies. LocalStack starts the Lambda 24 container on the job's network, which made the first run warn "Docker 514 network rm failed"; the last step, run even after failures, fixed that. The third run's log:
gh run view 36186186288 --log | cut -f2,3 | sed -E 's/\t\S+Z /: /' \
| grep -E 'Image:|is healthy|installed package|findings$|Successfully c|"books'Set up job: Image: ubuntu-24.04
Initialize containers: localstack service is healthy.
Lint the templates: installed package cfn-lint 1.57.0, installed using Python 3.12.3
Lint the templates: cfn-lint: no findings
Deploy the stack: Successfully created/updated stack - booknest-api
Smoke-test the function: {"statusCode":200,"body":"{\"books\":1}"}
Each run starts empty, so it proves the templates deploy from nothing, not that they update cleanly.