From ac2bcf88d070b99317c2a936143b62c3ffdadfce Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Wed, 19 Nov 2025 15:57:01 +0000 Subject: [PATCH 01/35] First draft --- WORKSHOP.md | 165 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 153 insertions(+), 12 deletions(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index a943ebf..c4420f8 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -4,26 +4,167 @@ Workshop description. --- -## 1. Section one +## 1. Project setup and basic build -1. **Step one** - Instructions. +1. **Create project folder** + Create a new folder for your project in a sensible location, for example: - ```text - Any command that needs copying + ```shell + mkdir -p ~/Documents/daemon-labs/docker-aws-lambda ``` - > Any notes. + > You can either create this via a terminal window or your file explorer. + +2. **Open the new folder in your code editor** + + > If you are using VSCode, we can now do everything from within the code editor. + +3. **Create `Dockerfile`** + Add the following content: + + ```Dockerfile + FROM public.ecr.aws/lambda/nodejs:22 + ``` + +4. **Create `docker-compose.yaml`** + Add the following content to define your service: + + ```yaml + --- + services: + lambda: + build: . + ``` + + > You'll also notice that we're mounting a volume, this is to ensure any generated files are saved back to your local host folder. + +5. **Initial image check** + - Run the following command + + ```shell + docker compose build + ``` + + > If you now run `docker images`, you'll see a newly created image which should be around 226MB in size. + + - Run the following command + + ```shell + docker compose run -it --rm --entrypoint /bin/sh -v ./app:/var/task lambda + ``` + + > This command opens an interactive session with the container. + + - Run the following command + + ```shell + node --version + ``` + + > The output should start with `v22` followed by the latest minor and patch version. --- -## 2. Section two +## 2. Dependency management and TypeScript config + +1. **Initialise project and install dev dependencies** + - Run the following command + + ```shell + npm init -y + ``` + + > Notice how the `lambda` directory is automatically created on your host machine due to the volume mount. + + - Run the following command + + ```shell + npm add --save-dev @types/node@22 @types/aws-lambda @tsconfig/recommended typescript + ``` -1. **Step one** - Instructions. + > Notice this automatically creates a `package-lock.json` file. + > Even though dependencies have been installed, if you run `docker images` again, you'll see the image size hasn't changed because the `node_modules` were written to your local volume, not the image layer. - ```text - Any command that needs copying +3. **Exit the container** + - Run the following command + + ```shell + exit + ``` + + > We are now done with the interactive container at this stage and no longer need it. + +4. **Create `tsconfig.json`** + Create `tsconfig.json` and add the following content to configure the TypeScript compiler: + + ```json + { + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "outDir": "./build/dist" + } + } ``` - > Any notes. + > ℹ️ While you could auto-generate this file, our manual configuration using a recommended preset keeps the file minimal and clean. + +5. **Create source file and scripts** + - Create `./src/index.ts` with the following: + + ```typescript + import { Handler } from "aws-lambda"; + + export const handler: Handler = (event,context) => { + console.log("Hello world!"); + }; + ``` + + - Add the following to the `scripts` section in your `package.json`: + + ```json + "build": "npm run build:tsc && npm run build:dependencies", + "build:tsc": "rm -rf ./build/dist && tsc", + "build:dependencies": "rm -rf ./build/dependencies && mkdir -p ./build/dependencies/nodejs && cp ./package*.json ./build/dependencies/nodejs && npm ci --omit dev --prefix ./build/dependencies/nodejs", + ``` + +Update the `Dockerfile` + +```Dockerfile +FROM public.ecr.aws/lambda/nodejs:22 + +COPY ./app /var/task + +RUN npm ci && npm run build + +HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ + CMD [ "curl", "-I", "http://localhost:8080" ] + +CMD [ "build/dist/index.handler" ] +``` + +Run `docker compose up --build` +> This Lambda starts but nothing happens + +Kill the container `Ctrl+C` + +Add a new service to the `docker-compos.yaml` file + +```yaml +curl: + image: curlimages/curl + depends_on: + lambda: + condition: service_healthy + command: + - -s + - -d {} + - http://lambda:8080/2015-03-31/functions/function/invocations +``` + +Run `docker compose up --build` +> The Lambda and cURL containers start and execute, the cURL container responded with an exite code of 0 but is still hanging + +Kill the container `Ctrl+C` + +Run `docker compose up --build --abort-on-container-exit` +> The Lambda and cURL containers start and execute, the cURL container responded with an exite code of 0 and both containers shut down From 879f0606e3629e4fcef5f7d3e75197b1a7917b66 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Wed, 19 Nov 2025 15:59:59 +0000 Subject: [PATCH 02/35] Update WORKSHOP.md --- WORKSHOP.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index c4420f8..bcca784 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -33,7 +33,7 @@ Workshop description. --- services: lambda: - build: . + build: . ``` > You'll also notice that we're mounting a volume, this is to ensure any generated files are saved back to your local host folder. @@ -53,7 +53,7 @@ Workshop description. docker compose run -it --rm --entrypoint /bin/sh -v ./app:/var/task lambda ``` - > This command opens an interactive session with the container. + > This command opens an interactive session with the container. - Run the following command @@ -67,15 +67,14 @@ Workshop description. ## 2. Dependency management and TypeScript config -1. **Initialise project and install dev dependencies** +1. **Initialise project and install dev dependencies** - Run the following command - ```shell - npm init -y - ``` - - > Notice how the `lambda` directory is automatically created on your host machine due to the volume mount. + ```shell + npm init -y + ``` + > Notice how the `lambda` directory is automatically created on your host machine due to the volume mount. - Run the following command ```shell @@ -85,7 +84,7 @@ Workshop description. > Notice this automatically creates a `package-lock.json` file. > Even though dependencies have been installed, if you run `docker images` again, you'll see the image size hasn't changed because the `node_modules` were written to your local volume, not the image layer. -3. **Exit the container** +2. **Exit the container** - Run the following command ```shell @@ -94,7 +93,7 @@ Workshop description. > We are now done with the interactive container at this stage and no longer need it. -4. **Create `tsconfig.json`** +3. **Create `tsconfig.json`** Create `tsconfig.json` and add the following content to configure the TypeScript compiler: ```json @@ -108,13 +107,13 @@ Workshop description. > ℹ️ While you could auto-generate this file, our manual configuration using a recommended preset keeps the file minimal and clean. -5. **Create source file and scripts** +4. **Create source file and scripts** - Create `./src/index.ts` with the following: ```typescript import { Handler } from "aws-lambda"; - export const handler: Handler = (event,context) => { + export const handler: Handler = (event, context) => { console.log("Hello world!"); }; ``` @@ -143,6 +142,7 @@ CMD [ "build/dist/index.handler" ] ``` Run `docker compose up --build` + > This Lambda starts but nothing happens Kill the container `Ctrl+C` @@ -151,20 +151,22 @@ Add a new service to the `docker-compos.yaml` file ```yaml curl: - image: curlimages/curl - depends_on: - lambda: - condition: service_healthy - command: - - -s - - -d {} - - http://lambda:8080/2015-03-31/functions/function/invocations + image: curlimages/curl + depends_on: + lambda: + condition: service_healthy + command: + - -s + - -d {} + - http://lambda:8080/2015-03-31/functions/function/invocations ``` Run `docker compose up --build` + > The Lambda and cURL containers start and execute, the cURL container responded with an exite code of 0 but is still hanging Kill the container `Ctrl+C` Run `docker compose up --build --abort-on-container-exit` + > The Lambda and cURL containers start and execute, the cURL container responded with an exite code of 0 and both containers shut down From 19788bb9aeaea070612480ba9b21777000238580 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 12:13:46 +0000 Subject: [PATCH 03/35] Update workshop instructions for clarity and order --- WORKSHOP.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index bcca784..a191445 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -13,20 +13,21 @@ Workshop description. mkdir -p ~/Documents/daemon-labs/docker-aws-lambda ``` + > [!NOTE] > You can either create this via a terminal window or your file explorer. -2. **Open the new folder in your code editor** +3. **Open the new folder in your code editor** > If you are using VSCode, we can now do everything from within the code editor. -3. **Create `Dockerfile`** +4. **Create `Dockerfile`** Add the following content: ```Dockerfile FROM public.ecr.aws/lambda/nodejs:22 ``` -4. **Create `docker-compose.yaml`** +5. **Create `docker-compose.yaml`** Add the following content to define your service: ```yaml @@ -38,7 +39,7 @@ Workshop description. > You'll also notice that we're mounting a volume, this is to ensure any generated files are saved back to your local host folder. -5. **Initial image check** +6. **Initial image check** - Run the following command ```shell From eb6508b6918936937f0a621fc1a759a71f326e82 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 12:14:11 +0000 Subject: [PATCH 04/35] Fix formatting of note in WORKSHOP.md --- WORKSHOP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index a191445..2573101 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -13,7 +13,7 @@ Workshop description. mkdir -p ~/Documents/daemon-labs/docker-aws-lambda ``` - > [!NOTE] + > [!NOTE] > You can either create this via a terminal window or your file explorer. 3. **Open the new folder in your code editor** From f0ddd3994b9a4a18d77bfdaced9de82a2e232b4a Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 12:16:12 +0000 Subject: [PATCH 05/35] Fix note formatting in WORKSHOP.md --- WORKSHOP.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index 2573101..4c942e6 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -13,8 +13,8 @@ Workshop description. mkdir -p ~/Documents/daemon-labs/docker-aws-lambda ``` - > [!NOTE] - > You can either create this via a terminal window or your file explorer. +> [!NOTE] +> You can either create this via a terminal window or your file explorer. 3. **Open the new folder in your code editor** From f9db5f12148ab6d71fa4c63ff506fbb5646774d8 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 12:16:40 +0000 Subject: [PATCH 06/35] Fix formatting in project setup section Correct formatting for project setup instructions. --- WORKSHOP.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index 4c942e6..6bd895d 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -7,11 +7,11 @@ Workshop description. ## 1. Project setup and basic build 1. **Create project folder** - Create a new folder for your project in a sensible location, for example: +Create a new folder for your project in a sensible location, for example: - ```shell - mkdir -p ~/Documents/daemon-labs/docker-aws-lambda - ``` +```shell +mkdir -p ~/Documents/daemon-labs/docker-aws-lambda +``` > [!NOTE] > You can either create this via a terminal window or your file explorer. From 67936e899c5088c91560256e3335bf42f1295612 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 12:18:31 +0000 Subject: [PATCH 07/35] Update tip formatting for VSCode instructions --- WORKSHOP.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index 6bd895d..ef55a98 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -18,7 +18,8 @@ mkdir -p ~/Documents/daemon-labs/docker-aws-lambda 3. **Open the new folder in your code editor** - > If you are using VSCode, we can now do everything from within the code editor. +> [!TIP] +> If you are using VSCode, we can now do everything from within the code editor. 4. **Create `Dockerfile`** Add the following content: From 8c8b8cc66269beea1cc64455a6fb8f7805f6ea78 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 12:18:46 +0000 Subject: [PATCH 08/35] Fix step numbering in WORKSHOP.md --- WORKSHOP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index ef55a98..1d6701f 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -16,7 +16,7 @@ mkdir -p ~/Documents/daemon-labs/docker-aws-lambda > [!NOTE] > You can either create this via a terminal window or your file explorer. -3. **Open the new folder in your code editor** +2. **Open the new folder in your code editor** > [!TIP] > If you are using VSCode, we can now do everything from within the code editor. From ebc5f276ecdeed773e2fd0d6343baaa6bc7be5de Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 12:19:03 +0000 Subject: [PATCH 09/35] Enhance project setup instructions with mkdir example Added example command for creating project folder. --- WORKSHOP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index 1d6701f..77cbe56 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -6,7 +6,7 @@ Workshop description. ## 1. Project setup and basic build -1. **Create project folder** +1. **Create project folder** Create a new folder for your project in a sensible location, for example: ```shell From 3ab8c913cc71b26efb6cb17c737d0fb6170e0fbf Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Thu, 20 Nov 2025 14:03:19 +0000 Subject: [PATCH 10/35] Update WORKSHOP.md --- WORKSHOP.md | 171 +++++++++++++++++++++++++++++----------------------- 1 file changed, 95 insertions(+), 76 deletions(-) diff --git a/WORKSHOP.md b/WORKSHOP.md index 77cbe56..881e736 100644 --- a/WORKSHOP.md +++ b/WORKSHOP.md @@ -6,127 +6,146 @@ Workshop description. ## 1. Project setup and basic build -1. **Create project folder** +### Create project folder + Create a new folder for your project in a sensible location, for example: ```shell mkdir -p ~/Documents/daemon-labs/docker-aws-lambda ``` -> [!NOTE] +> [!NOTE] > You can either create this via a terminal window or your file explorer. -2. **Open the new folder in your code editor** +### Open the new folder in your code editor > [!TIP] > If you are using VSCode, we can now do everything from within the code editor. -4. **Create `Dockerfile`** - Add the following content: +### Create `Dockerfile` - ```Dockerfile - FROM public.ecr.aws/lambda/nodejs:22 - ``` +Add the following content: -5. **Create `docker-compose.yaml`** - Add the following content to define your service: +```Dockerfile +FROM public.ecr.aws/lambda/nodejs:22 +``` - ```yaml - --- - services: - lambda: - build: . - ``` +### Create `docker-compose.yaml` - > You'll also notice that we're mounting a volume, this is to ensure any generated files are saved back to your local host folder. +Add the following content to define your service: -6. **Initial image check** - - Run the following command +```yaml +--- +services: + lambda: + build: . +``` - ```shell - docker compose build - ``` +> [!NOTE] +> You'll also notice that we're mounting a volume, this is to ensure any generated files are saved back to your local host folder. - > If you now run `docker images`, you'll see a newly created image which should be around 226MB in size. +### Initial image check - - Run the following command +Run the following command: - ```shell - docker compose run -it --rm --entrypoint /bin/sh -v ./app:/var/task lambda - ``` +```shell +docker compose build +``` + +> [!NOTE] +> If you now run `docker images`, you'll see a newly created image which should be around 226MB in size. + +Run the following command: + +```shell +docker compose run -it --rm --entrypoint /bin/sh -v ./app:/var/task lambda +``` - > This command opens an interactive session with the container. +> [!NOTE] +> This command opens an interactive session with the container. - - Run the following command +Run the following command: - ```shell - node --version - ``` +```shell +node --version +``` - > The output should start with `v22` followed by the latest minor and patch version. +> [!NOTE] +> The output should start with `v22` followed by the latest minor and patch version. --- ## 2. Dependency management and TypeScript config -1. **Initialise project and install dev dependencies** - - Run the following command +### Initialise project and install dev dependencies - ```shell - npm init -y - ``` +Run the following command: - > Notice how the `lambda` directory is automatically created on your host machine due to the volume mount. - - Run the following command +```shell +npm init -y +``` + +> [!NOTE] +> Notice how the `lambda` directory is automatically created on your host machine due to the volume mount. + +Run the following command: + +```shell +npm add --save-dev @types/node@22 @types/aws-lambda @tsconfig/recommended typescript +``` + +> [!NOTE] +> Notice this automatically creates a `package-lock.json` file. +> Even though dependencies have been installed, if you run `docker images` again, you'll see the image size hasn't changed because the `node_modules` were written to your local volume, not the image layer. - ```shell - npm add --save-dev @types/node@22 @types/aws-lambda @tsconfig/recommended typescript - ``` +### Exit the container - > Notice this automatically creates a `package-lock.json` file. - > Even though dependencies have been installed, if you run `docker images` again, you'll see the image size hasn't changed because the `node_modules` were written to your local volume, not the image layer. +Run the following command: -2. **Exit the container** - - Run the following command +```shell +exit +``` + +> [!NOTE] +> We are now done with the interactive container at this stage and no longer need it. + +### Create `tsconfig.json` - ```shell - exit - ``` +Create `tsconfig.json` and add the following content to configure the TypeScript compiler: - > We are now done with the interactive container at this stage and no longer need it. +```json +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "outDir": "./build/dist" + } +} +``` -3. **Create `tsconfig.json`** - Create `tsconfig.json` and add the following content to configure the TypeScript compiler: +> [!NOTE] +> While you could auto-generate this file, our manual configuration using a recommended preset keeps the file minimal and clean. - ```json - { - "extends": "@tsconfig/recommended/tsconfig.json", - "compilerOptions": { - "outDir": "./build/dist" - } - } - ``` +### Create source file and scripts - > ℹ️ While you could auto-generate this file, our manual configuration using a recommended preset keeps the file minimal and clean. +Create `./src/index.ts` with the following: -4. **Create source file and scripts** - - Create `./src/index.ts` with the following: +```typescript +import { Handler } from "aws-lambda"; - ```typescript - import { Handler } from "aws-lambda"; +export const handler: Handler = (event, context) => { + console.log("Hello world!"); +}; +``` - export const handler: Handler = (event, context) => { - console.log("Hello world!"); - }; - ``` +Add the following to the `scripts` section in your `package.json`: - - Add the following to the `scripts` section in your `package.json`: +```json +"build": "npm run build:tsc && npm run build:dependencies", +"build:tsc": "rm -rf ./build/dist && tsc", +"build:dependencies": "rm -rf ./build/dependencies && mkdir -p ./build/dependencies/nodejs && cp ./package*.json ./build/dependencies/nodejs && npm ci --omit dev --prefix ./build/dependencies/nodejs", +``` - ```json - "build": "npm run build:tsc && npm run build:dependencies", - "build:tsc": "rm -rf ./build/dist && tsc", - "build:dependencies": "rm -rf ./build/dependencies && mkdir -p ./build/dependencies/nodejs && cp ./package*.json ./build/dependencies/nodejs && npm ci --omit dev --prefix ./build/dependencies/nodejs", - ``` +--- Update the `Dockerfile` From f273f51801aadf42ea908daf568cd174f3c3c232 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 14:06:09 +0000 Subject: [PATCH 11/35] Various updates --- CODE_OF_CONDUCT.md | 83 ----------- CONTRIBUTING.md | 43 ------ README.md | 333 ++++++++++++++++++++++++++++++++++++++++++++- SECURITY.md | 27 ---- WORKSHOP.md | 193 -------------------------- 5 files changed, 326 insertions(+), 353 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 CONTRIBUTING.md delete mode 100644 SECURITY.md delete mode 100644 WORKSHOP.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 0952d22..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,83 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of actions. - -**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 9672e0c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contributing - -We welcome contributions to this project! This repository is primarily a **tutorial and educational resource**, so we are focused on changes that enhance clarity, correctness, and pedagogical value. By participating, you agree to abide by our [code of conduct](./CODE_OF_CONDUCT.md). - ---- - -## Ways to contribute - -### 1. Reporting issues - -- **Found a bug?** If the instructions or code examples don't work, please open a new **Issue**. Be sure to include your operating system, Docker version, and the exact steps to reproduce the error. -- **Suggesting Enhancements?** If you have an idea to make the tutorial clearer or suggest a common, robust alternative approach (e.g., a different base image), feel free to open an Issue to discuss it first. - -### 2. Submitting pull requests (PRs) - -We are looking for PRs that: - -- **Fix typos or incorrect commands** in the `README.md` or configuration files. -- **Improve the explanation or clarity** of the steps. -- **Correct any non-functional code** in `src/index.ts` or configuration files. - -## Pull request guidelines - -1. **Fork** the repository to your own GitHub account. -2. **Clone** your fork locally and navigate into the directory: - ```bash - git clone [https://github.com/YOUR_USERNAME/workshop-template.git](https://github.com/YOUR_USERNAME/workshop-template.git) - cd workshop-template - ``` -3. **Create a new, descriptive branch** for your changes (e.g., `fix/typo-in-readme`). -4. **Make your changes**, ensuring you run `docker compose up` to verify that the project still works after your changes. -5. **Write clear and concise commit messages.** Each commit should address a single logical change. -6. **Push your branch** and open a **Pull Request** against the `main` branch of this repository. - -Please clearly describe what you changed and why in the PR description. - ---- - -## Code of conduct - -Please review and adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md) in all your interactions with the project and its community. - -Thank you for helping us make this a better resource! diff --git a/README.md b/README.md index 7fc4b80..06fc877 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# 📚 Daemon Labs workshop documentation hub +# Learn to build and develop AWS Lambda functions locally with Docker -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md) +@todo Workshop description. + +--- ## 🛑 Prerequisites @@ -8,16 +10,333 @@ Before beginning this workshop, please ensure your environment is correctly set ➡️ **[Prerequisites guide](https://github.com/daemon-labs-resources/prerequisites)** +Run the following command: + +```shell +docker pull ... +``` + +> [!TIP] +> Pulling the Docker images isn't a requirement, but it helps to have it pre-downloaded so we don't wait for everyone to do it at the same time. + --- -## 📚 Workshop and full documentation +### Create project folder + +Create a new folder for your project in a sensible location, for example: + +```shell +mkdir -p ~/Documents/daemon-labs/docker-aws-lambda +``` + +> [!NOTE] +> You can either create this via a terminal window or your file explorer. -The detailed, step-by-step instructions and narrative for the project are contained in the main workshop document: +### Open the new folder in your code editor -➡️ **[View the workshop guide](./WORKSHOP.md)** +> [!TIP] +> If you are using VSCode, we can now do everything from within the code editor. +### Create `./nodejs` folder + +### Create `Dockerfile` + +Add the following content: + +```Dockerfile +FROM public.ecr.aws/lambda/nodejs:22 +``` + +### Create `docker-compose.yaml` + +Add the following content to define your service: + +```yaml --- +services: + lambda: + build: . +``` + +### Initial image check + +Run the following command: + +```shell +docker compose build +``` + +> [!NOTE] +> If you now run `docker images`, you'll see a newly created image which should be around 436MB in size. + +Run the following command: + +```shell +docker compose run -it --rm --entrypoint /bin/sh -v ./nodejs:/var/task lambda +``` + +> [!NOTE] +> This command opens an interactive session with the container. + +Run the following command: + +```shell +node --version +``` + +> [!NOTE] +> The output should start with `v22` followed by the latest minor and patch version. + +### Initialise project and install dev dependencies + +Run the following command: + +```shell +npm init -y +``` + +> [!NOTE] +> Notice how the `nodejs` directory is automatically created on your host machine due to the volume mount. + +Run the following command: + +```shell +npm add --save-dev @types/node@22 @types/aws-lambda @tsconfig/recommended typescript +``` + +> [!NOTE] +> Notice this automatically creates a `package-lock.json` file. + +### Exit the container + +Run the following command: + +```shell +exit +``` + +> [!NOTE] +> We are now done with the interactive container at this stage and no longer need it. + +### Create `./nodejs/tsconfig.json` + +Create `./nodejs/tsconfig.json` and add the following content to configure the TypeScript compiler: + +```json +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "outDir": "./build" + } +} +``` + +> [!NOTE] +> While you could auto-generate this file, our manual configuration using a recommended preset keeps the file minimal and clean. + +### Create source file and scripts + +Create `./nodejs/src/index.ts` with the following: + +```typescript +import { Handler } from "aws-lambda"; + +export const handler: Handler = (event, context) => { + console.log("Hello world!"); + console.log({ event, context }); +}; + +``` + +Add the following to the `scripts` section in your `package.json`: + +```json +"build": "tsc", +``` + +--- + +Update the `Dockerfile` + +```Dockerfile +FROM public.ecr.aws/lambda/nodejs:22 + +HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ + CMD [ "curl", "-I", "http://localhost:8080" ] + +COPY ./nodejs ${LAMBDA_TASK_ROOT} + +RUN npm ci && npm run build + +CMD [ "build/index.handler" ] +``` + +Run `docker compose up --build` + +> [!WARNING] +> This Lambda starts but nothing happens. +> **Exit your container by pressing `Ctrl+C`** on your keyboard. + +Add a new service to the `docker-compose.yaml` file + +```yaml +curl: + image: curlimages/curl + depends_on: + lambda: + condition: service_healthy + command: + - -s + - -d {} + - http://lambda:8080/2015-03-31/functions/function/invocations +``` + +Run `docker compose up --build` + +> [!WARNING] +> The Lambda and cURL containers start and execute, the cURL container responded with an exit code of 0 but is still hanging. +> **Exit your container by pressing `Ctrl+C`** on your keyboard. + +Run `docker compose up --build --abort-on-container-exit` + +> [!NOTE] +> The Lambda and cURL containers start and execute, the cURL container responded with an exit code of 0 and both containers shut down. + +Add the following environment variables to the `Dockerfile` + +```Dockerfile +ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 +ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 +``` + +> [!TIP] +> This replicates the default settings of an AWS Lambda. +> Without these the Docker image defaults to a memory size of 3008MB and potentially an infinite timeout. + +Add the following environment variable to the `Dockerfile` + +```Dockerfile +ENV AWS_LAMBDA_LOG_FORMAT=JSON +``` + +> [!TIP] +> When we executed our Lambda you might have noticed our two logs were printed as plain text. +> In fact, the `console.log({ event, context });` didn't actually print out anything useful at all. + +### Set up an event + +Create an `events` directory + +Create a `test.json` file, add the following and save: + +```json +{ + "test": "test" +} +``` + +### Update the command in `docker-compose.yaml` + +Update the command for the cURL container: + +```yaml +command: + - -s + - -d + - ${LAMBDA_INPUT:-{}} + - http://lambda:8080/2015-03-31/functions/function/invocations +``` + +> [!NOTE] +> By defining the command like this, by default it will pass in `{}` as the event still. + +Add the `events` directory as a volume to the cURL container: + +```yaml +volumes: + - ./events:/events:ro +``` + +Run `LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-container-exit` + +> [!TIP] +> The `@` tells the cURL command that it should include the contents of a file rather than passing as a string. + +### Update the `Dockerfile` + +```Dockerfile +FROM public.ecr.aws/lambda/nodejs:22 + +ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 +ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 +ENV AWS_LAMBDA_LOG_FORMAT=JSON + +HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ + CMD [ "curl", "-I", "http://localhost:8080" ] + +COPY ./nodejs/package*.json ${LAMBDA_TASK_ROOT} + +RUN npm ci + +COPY ./nodejs ${LAMBDA_TASK_ROOT} + +RUN npm run build + +CMD [ "build/index.handler" ] +``` + +### Update the `Dockerfile` + +```Dockerfile +FROM public.ecr.aws/lambda/nodejs:22 AS base + +ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 +ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 +ENV AWS_LAMBDA_LOG_FORMAT=JSON + +HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ + CMD [ "curl", "-I", "http://localhost:8080" ] + +FROM base AS build + +COPY ./nodejs/package*.json ${LAMBDA_TASK_ROOT} + +RUN npm ci + +COPY ./nodejs ${LAMBDA_TASK_ROOT} + +RUN npm run build + +FROM base + +COPY --from=build ${LAMBDA_TASK_ROOT}/package*.json ${LAMBDA_TASK_ROOT} +COPY --from=build ${LAMBDA_TASK_ROOT}/build ${LAMBDA_TASK_ROOT}/build + +RUN npm ci --only=production + +CMD [ "build/index.handler" ] +``` + +### Create a `python` directory + +### Create a `./python/Dockerfile` + +### Create the `./python/requirements.txt` + +### Create the handler file at `./python/app.py` + +### Update `docker-compose.yaml` + +### Run the Lambda + +- SAM + +- cleanup + +--- + +## 🎉 Congratulations -## Contributing +You've just learnt to build and develop AWS Lambda functions locally with Docker. -Please see our [CONTRIBUTING.md](./CONTRIBUTING.md) for more details on submitting changes or improvements to this documentation. +### Recap of what you built diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 6166f0b..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Security Policy - -## Reporting a vulnerability - -We take the security of this project seriously. While this repository is primarily an educational resource and is **not intended for production deployment**, we still encourage users to report any security vulnerabilities they discover. - -**Please DO NOT report security issues via public GitHub Issues or Pull Requests.** - -Instead, please report it privately by using GitHub's **"Report a vulnerability"** feature. -You can find this option under the **Security** tab of this repository. - -### Preferred information - -To help us address the issue quickly, please include the following in your report: - -- A brief description of the vulnerability. -- The steps required to reproduce the issue. -- Your environment details (e.g., Docker version, operating system). -- Any suggested mitigations or fixes (if known). - -We will acknowledge your report within 48 hours and provide a detailed response to the reported vulnerability within 7 days. - -## Scope - -The scope of this policy covers the code and configuration files within the `daemon-labs-resources/workshop-template` repository. - -Thank you for helping to keep this project secure. diff --git a/WORKSHOP.md b/WORKSHOP.md deleted file mode 100644 index 881e736..0000000 --- a/WORKSHOP.md +++ /dev/null @@ -1,193 +0,0 @@ -# Workshop title - -Workshop description. - ---- - -## 1. Project setup and basic build - -### Create project folder - -Create a new folder for your project in a sensible location, for example: - -```shell -mkdir -p ~/Documents/daemon-labs/docker-aws-lambda -``` - -> [!NOTE] -> You can either create this via a terminal window or your file explorer. - -### Open the new folder in your code editor - -> [!TIP] -> If you are using VSCode, we can now do everything from within the code editor. - -### Create `Dockerfile` - -Add the following content: - -```Dockerfile -FROM public.ecr.aws/lambda/nodejs:22 -``` - -### Create `docker-compose.yaml` - -Add the following content to define your service: - -```yaml ---- -services: - lambda: - build: . -``` - -> [!NOTE] -> You'll also notice that we're mounting a volume, this is to ensure any generated files are saved back to your local host folder. - -### Initial image check - -Run the following command: - -```shell -docker compose build -``` - -> [!NOTE] -> If you now run `docker images`, you'll see a newly created image which should be around 226MB in size. - -Run the following command: - -```shell -docker compose run -it --rm --entrypoint /bin/sh -v ./app:/var/task lambda -``` - -> [!NOTE] -> This command opens an interactive session with the container. - -Run the following command: - -```shell -node --version -``` - -> [!NOTE] -> The output should start with `v22` followed by the latest minor and patch version. - ---- - -## 2. Dependency management and TypeScript config - -### Initialise project and install dev dependencies - -Run the following command: - -```shell -npm init -y -``` - -> [!NOTE] -> Notice how the `lambda` directory is automatically created on your host machine due to the volume mount. - -Run the following command: - -```shell -npm add --save-dev @types/node@22 @types/aws-lambda @tsconfig/recommended typescript -``` - -> [!NOTE] -> Notice this automatically creates a `package-lock.json` file. -> Even though dependencies have been installed, if you run `docker images` again, you'll see the image size hasn't changed because the `node_modules` were written to your local volume, not the image layer. - -### Exit the container - -Run the following command: - -```shell -exit -``` - -> [!NOTE] -> We are now done with the interactive container at this stage and no longer need it. - -### Create `tsconfig.json` - -Create `tsconfig.json` and add the following content to configure the TypeScript compiler: - -```json -{ - "extends": "@tsconfig/recommended/tsconfig.json", - "compilerOptions": { - "outDir": "./build/dist" - } -} -``` - -> [!NOTE] -> While you could auto-generate this file, our manual configuration using a recommended preset keeps the file minimal and clean. - -### Create source file and scripts - -Create `./src/index.ts` with the following: - -```typescript -import { Handler } from "aws-lambda"; - -export const handler: Handler = (event, context) => { - console.log("Hello world!"); -}; -``` - -Add the following to the `scripts` section in your `package.json`: - -```json -"build": "npm run build:tsc && npm run build:dependencies", -"build:tsc": "rm -rf ./build/dist && tsc", -"build:dependencies": "rm -rf ./build/dependencies && mkdir -p ./build/dependencies/nodejs && cp ./package*.json ./build/dependencies/nodejs && npm ci --omit dev --prefix ./build/dependencies/nodejs", -``` - ---- - -Update the `Dockerfile` - -```Dockerfile -FROM public.ecr.aws/lambda/nodejs:22 - -COPY ./app /var/task - -RUN npm ci && npm run build - -HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ - CMD [ "curl", "-I", "http://localhost:8080" ] - -CMD [ "build/dist/index.handler" ] -``` - -Run `docker compose up --build` - -> This Lambda starts but nothing happens - -Kill the container `Ctrl+C` - -Add a new service to the `docker-compos.yaml` file - -```yaml -curl: - image: curlimages/curl - depends_on: - lambda: - condition: service_healthy - command: - - -s - - -d {} - - http://lambda:8080/2015-03-31/functions/function/invocations -``` - -Run `docker compose up --build` - -> The Lambda and cURL containers start and execute, the cURL container responded with an exite code of 0 but is still hanging - -Kill the container `Ctrl+C` - -Run `docker compose up --build --abort-on-container-exit` - -> The Lambda and cURL containers start and execute, the cURL container responded with an exite code of 0 and both containers shut down From 2b346159a07ed4cc94a2165edee9a7141571cbee Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 14:06:20 +0000 Subject: [PATCH 12/35] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 191b3b9..1396d0b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ !.github !.github/**/* !.gitignore -!*.md +!LICENSE.md +!README.md From a33ef83d86e65cb3316f196a5325d55ad2b00a41 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 14:15:45 +0000 Subject: [PATCH 13/35] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 06fc877..3fceb76 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,9 @@ Create `./nodejs/src/index.ts` with the following: import { Handler } from "aws-lambda"; export const handler: Handler = (event, context) => { - console.log("Hello world!"); - console.log({ event, context }); + console.log("Hello world!"); + console.log({ event, context }); }; - ``` Add the following to the `scripts` section in your `package.json`: @@ -242,7 +241,7 @@ Update the command for the cURL container: ```yaml command: - -s - - -d + - -d - ${LAMBDA_INPUT:-{}} - http://lambda:8080/2015-03-31/functions/function/invocations ``` From 42e2bf727b1f5ce148bbe9bb255b389ffa631cd7 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 14:20:55 +0000 Subject: [PATCH 14/35] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3fceb76..3490fea 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ Run `LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-contai > [!TIP] > The `@` tells the cURL command that it should include the contents of a file rather than passing as a string. -### Update the `Dockerfile` +### Update the `Dockerfile` for optimised caching ```Dockerfile FROM public.ecr.aws/lambda/nodejs:22 @@ -284,7 +284,7 @@ RUN npm run build CMD [ "build/index.handler" ] ``` -### Update the `Dockerfile` +### Update the `Dockerfile` for multi-stage builds ```Dockerfile FROM public.ecr.aws/lambda/nodejs:22 AS base From 1c5c41c3b0a557c853154ba5f1448c84142c90a3 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:30:06 +0000 Subject: [PATCH 15/35] Update README.md --- README.md | 51 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3490fea..0ab3d63 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,51 @@ Before beginning this workshop, please ensure your environment is correctly set ➡️ **[Prerequisites guide](https://github.com/daemon-labs-resources/prerequisites)** +### Retrieve Docker images + +#### Load Docker images + +> [!WARNING] +> This only works when attending a workshop in person. +> Due to having a number of people trying to retrieve Docker images at the same time, this allows for a more efficient way. + +Once the facilitator has given you an IP address, open `http://:8000` in your browser. + +When you see the file listing, download the `workshop-images.tar` file. + +> [!WARNING] +> Your browser may block the download initially, allow it to download. + Run the following command: ```shell -docker pull ... +docker load -i ~/Downloads/workshop-images.tar ``` -> [!TIP] -> Pulling the Docker images isn't a requirement, but it helps to have it pre-downloaded so we don't wait for everyone to do it at the same time. +#### Pull Docker images + +> [!CAUTION] +> Only use this approach if you are running through this workshop on your own. + +Run the following command: + +```shell +docker pull public.ecr.aws/lambda/nodejs:24 +docker pull public.ecr.aws/lambda/python:3.14 +docker pull curlimages/curl +docker pull localstack/localstack +``` + +### Validate Docker images + +Run the following command: + +```shell +docker images +``` + +> [!NOTE] +> You should now see four images listed. --- @@ -44,7 +81,7 @@ mkdir -p ~/Documents/daemon-labs/docker-aws-lambda Add the following content: ```Dockerfile -FROM public.ecr.aws/lambda/nodejs:22 +FROM public.ecr.aws/lambda/nodejs:24 ``` ### Create `docker-compose.yaml` @@ -158,7 +195,7 @@ Add the following to the `scripts` section in your `package.json`: Update the `Dockerfile` ```Dockerfile -FROM public.ecr.aws/lambda/nodejs:22 +FROM public.ecr.aws/lambda/nodejs:24 HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ CMD [ "curl", "-I", "http://localhost:8080" ] @@ -264,7 +301,7 @@ Run `LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-contai ### Update the `Dockerfile` for optimised caching ```Dockerfile -FROM public.ecr.aws/lambda/nodejs:22 +FROM public.ecr.aws/lambda/nodejs:24 ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 @@ -287,7 +324,7 @@ CMD [ "build/index.handler" ] ### Update the `Dockerfile` for multi-stage builds ```Dockerfile -FROM public.ecr.aws/lambda/nodejs:22 AS base +FROM public.ecr.aws/lambda/nodejs:24 AS base ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 From 4a75e179f98679ebbfdcfe0fe3b021b485a81fa2 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:30:41 +0000 Subject: [PATCH 16/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ab3d63..d126812 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Before beginning this workshop, please ensure your environment is correctly set #### Load Docker images -> [!WARNING] +> [!CAUTION] > This only works when attending a workshop in person. > Due to having a number of people trying to retrieve Docker images at the same time, this allows for a more efficient way. From fa485a81f67587be43d40a5c59e492a507f94020 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:32:00 +0000 Subject: [PATCH 17/35] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d126812..a7dfe12 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Before beginning this workshop, please ensure your environment is correctly set > [!CAUTION] > This only works when attending a workshop in person. > Due to having a number of people trying to retrieve Docker images at the same time, this allows for a more efficient way. +> +> If you are **NOT** in an in-person workshop, see [pull docker images](#pull-docker-images). Once the facilitator has given you an IP address, open `http://:8000` in your browser. @@ -35,6 +37,8 @@ docker load -i ~/Downloads/workshop-images.tar > [!CAUTION] > Only use this approach if you are running through this workshop on your own. +> +> If you are in an in-person workshop, see [load docker images](#load-docker-images). Run the following command: From 71c61222d75eb49b0ea45492e413dd52c43cc411 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:33:13 +0000 Subject: [PATCH 18/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7dfe12..18a0738 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Once the facilitator has given you an IP address, open `http://:8000 When you see the file listing, download the `workshop-images.tar` file. > [!WARNING] -> Your browser may block the download initially, allow it to download. +> Your browser may block the download initially, when prompted, allow it to download. Run the following command: From 0e51bee934b116959d5b0e92253097d972e97551 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:43:41 +0000 Subject: [PATCH 19/35] Update README.md --- README.md | 302 ++++-------------------------------------------------- 1 file changed, 20 insertions(+), 282 deletions(-) diff --git a/README.md b/README.md index 18a0738..73899e3 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,13 @@ docker images --- +## 1. The foundation + +**Goal:** Get a working container environment running. + ### Create project folder -Create a new folder for your project in a sensible location, for example: +Create a new folder for your project: ```shell mkdir -p ~/Documents/daemon-labs/docker-aws-lambda @@ -76,13 +80,20 @@ mkdir -p ~/Documents/daemon-labs/docker-aws-lambda ### Open the new folder in your code editor > [!TIP] -> If you are using VSCode, we can now do everything from within the code editor. +> If you are using VSCode, we can now do everything from within the code editor. +> You can open the terminal window via Terminal -> New Terminal. + +### Create the code subdirectory -### Create `./nodejs` folder +We keep our application code separate from infrastructure config. + +```shell +mkdir nodejs +``` -### Create `Dockerfile` +### Create the `Dockerfile` -Add the following content: +Create the file at `nodejs/Dockerfile` (inside the subdirectory). ```Dockerfile FROM public.ecr.aws/lambda/nodejs:24 @@ -90,293 +101,20 @@ FROM public.ecr.aws/lambda/nodejs:24 ### Create `docker-compose.yaml` -Add the following content to define your service: +Create this file in the **root** of your project. ```yaml ---- services: lambda: - build: . -``` - -### Initial image check - -Run the following command: - -```shell -docker compose build + build: ./nodejs ``` -> [!NOTE] -> If you now run `docker images`, you'll see a newly created image which should be around 436MB in size. +### Initialise the container -Run the following command: +Run this command to start an interactive shell. ```shell docker compose run -it --rm --entrypoint /bin/sh -v ./nodejs:/var/task lambda ``` -> [!NOTE] -> This command opens an interactive session with the container. - -Run the following command: - -```shell -node --version -``` - -> [!NOTE] -> The output should start with `v22` followed by the latest minor and patch version. - -### Initialise project and install dev dependencies - -Run the following command: - -```shell -npm init -y -``` - -> [!NOTE] -> Notice how the `nodejs` directory is automatically created on your host machine due to the volume mount. - -Run the following command: - -```shell -npm add --save-dev @types/node@22 @types/aws-lambda @tsconfig/recommended typescript -``` - -> [!NOTE] -> Notice this automatically creates a `package-lock.json` file. - -### Exit the container - -Run the following command: - -```shell -exit -``` - -> [!NOTE] -> We are now done with the interactive container at this stage and no longer need it. - -### Create `./nodejs/tsconfig.json` - -Create `./nodejs/tsconfig.json` and add the following content to configure the TypeScript compiler: - -```json -{ - "extends": "@tsconfig/recommended/tsconfig.json", - "compilerOptions": { - "outDir": "./build" - } -} -``` - -> [!NOTE] -> While you could auto-generate this file, our manual configuration using a recommended preset keeps the file minimal and clean. - -### Create source file and scripts - -Create `./nodejs/src/index.ts` with the following: - -```typescript -import { Handler } from "aws-lambda"; - -export const handler: Handler = (event, context) => { - console.log("Hello world!"); - console.log({ event, context }); -}; -``` - -Add the following to the `scripts` section in your `package.json`: - -```json -"build": "tsc", -``` - ---- - -Update the `Dockerfile` - -```Dockerfile -FROM public.ecr.aws/lambda/nodejs:24 - -HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ - CMD [ "curl", "-I", "http://localhost:8080" ] - -COPY ./nodejs ${LAMBDA_TASK_ROOT} - -RUN npm ci && npm run build - -CMD [ "build/index.handler" ] -``` - -Run `docker compose up --build` - -> [!WARNING] -> This Lambda starts but nothing happens. -> **Exit your container by pressing `Ctrl+C`** on your keyboard. - -Add a new service to the `docker-compose.yaml` file - -```yaml -curl: - image: curlimages/curl - depends_on: - lambda: - condition: service_healthy - command: - - -s - - -d {} - - http://lambda:8080/2015-03-31/functions/function/invocations -``` - -Run `docker compose up --build` - -> [!WARNING] -> The Lambda and cURL containers start and execute, the cURL container responded with an exit code of 0 but is still hanging. -> **Exit your container by pressing `Ctrl+C`** on your keyboard. - -Run `docker compose up --build --abort-on-container-exit` - -> [!NOTE] -> The Lambda and cURL containers start and execute, the cURL container responded with an exit code of 0 and both containers shut down. - -Add the following environment variables to the `Dockerfile` - -```Dockerfile -ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 -``` - -> [!TIP] -> This replicates the default settings of an AWS Lambda. -> Without these the Docker image defaults to a memory size of 3008MB and potentially an infinite timeout. - -Add the following environment variable to the `Dockerfile` - -```Dockerfile -ENV AWS_LAMBDA_LOG_FORMAT=JSON -``` - -> [!TIP] -> When we executed our Lambda you might have noticed our two logs were printed as plain text. -> In fact, the `console.log({ event, context });` didn't actually print out anything useful at all. - -### Set up an event - -Create an `events` directory - -Create a `test.json` file, add the following and save: - -```json -{ - "test": "test" -} -``` - -### Update the command in `docker-compose.yaml` - -Update the command for the cURL container: - -```yaml -command: - - -s - - -d - - ${LAMBDA_INPUT:-{}} - - http://lambda:8080/2015-03-31/functions/function/invocations -``` - -> [!NOTE] -> By defining the command like this, by default it will pass in `{}` as the event still. - -Add the `events` directory as a volume to the cURL container: - -```yaml -volumes: - - ./events:/events:ro -``` - -Run `LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-container-exit` - -> [!TIP] -> The `@` tells the cURL command that it should include the contents of a file rather than passing as a string. - -### Update the `Dockerfile` for optimised caching - -```Dockerfile -FROM public.ecr.aws/lambda/nodejs:24 - -ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 -ENV AWS_LAMBDA_LOG_FORMAT=JSON - -HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ - CMD [ "curl", "-I", "http://localhost:8080" ] - -COPY ./nodejs/package*.json ${LAMBDA_TASK_ROOT} - -RUN npm ci - -COPY ./nodejs ${LAMBDA_TASK_ROOT} - -RUN npm run build - -CMD [ "build/index.handler" ] -``` - -### Update the `Dockerfile` for multi-stage builds - -```Dockerfile -FROM public.ecr.aws/lambda/nodejs:24 AS base - -ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 -ENV AWS_LAMBDA_LOG_FORMAT=JSON - -HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ - CMD [ "curl", "-I", "http://localhost:8080" ] - -FROM base AS build - -COPY ./nodejs/package*.json ${LAMBDA_TASK_ROOT} - -RUN npm ci - -COPY ./nodejs ${LAMBDA_TASK_ROOT} - -RUN npm run build - -FROM base - -COPY --from=build ${LAMBDA_TASK_ROOT}/package*.json ${LAMBDA_TASK_ROOT} -COPY --from=build ${LAMBDA_TASK_ROOT}/build ${LAMBDA_TASK_ROOT}/build - -RUN npm ci --only=production - -CMD [ "build/index.handler" ] -``` - -### Create a `python` directory - -### Create a `./python/Dockerfile` - -### Create the `./python/requirements.txt` - -### Create the handler file at `./python/app.py` - -### Update `docker-compose.yaml` - -### Run the Lambda - -- SAM - -- cleanup - --- - -## 🎉 Congratulations - -You've just learnt to build and develop AWS Lambda functions locally with Docker. - -### Recap of what you built From 8debe1d6dabf2852e7262baae41a64030c67863e Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:49:17 +0000 Subject: [PATCH 20/35] Update README.md --- README.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73899e3..c20b204 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ mkdir -p ~/Documents/daemon-labs/docker-aws-lambda > [!TIP] > If you are using VSCode, we can now do everything from within the code editor. -> You can open the terminal window via Terminal -> New Terminal. +> You can open the terminal pane via Terminal -> New Terminal. ### Create the code subdirectory @@ -93,7 +93,7 @@ mkdir nodejs ### Create the `Dockerfile` -Create the file at `nodejs/Dockerfile` (inside the subdirectory). +Create the file at `./nodejs/Dockerfile` (inside the subdirectory). ```Dockerfile FROM public.ecr.aws/lambda/nodejs:24 @@ -118,3 +118,68 @@ docker compose run -it --rm --entrypoint /bin/sh -v ./nodejs:/var/task lambda ``` --- + +## 2. The application + +**Goal:** Initialise a TypeScript Node.js project. + +### Initialise the project + +Inside the container shell: + +```shell +npm init -y +``` + +### Install dependencies + +```shell +npm add --save-dev @types/node@24 @types/aws-lambda @tsconfig/recommended typescript +``` + +### Exit the container + +```shell +exit +``` + +### Configure TypeScript + +Create `./nodejs/tsconfig.json` locally: + +```json +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "outDir": "./build" + } +} +``` + +### Create the handler + +Create `nodejs/src/index.ts`: + +```typescript +import { Handler } from "aws-lambda"; + +export const handler: Handler = (event, context) => { + console.log("Hello world!"); + console.log({ event, context }); + + return { + statusCode: 200, + body: "Hello World!" + }; +}; +``` + +### Add build script + +Update `./nodejs/package.json` scripts: + +```json +"scripts": { + "build": "tsc" +}, +``` From 9b3cf58eafc019f070f128d3715212cba1ca0ef6 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:49:33 +0000 Subject: [PATCH 21/35] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c20b204..9a9894e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Before beginning this workshop, please ensure your environment is correctly set > [!CAUTION] > This only works when attending a workshop in person. > Due to having a number of people trying to retrieve Docker images at the same time, this allows for a more efficient way. -> +> > If you are **NOT** in an in-person workshop, see [pull docker images](#pull-docker-images). Once the facilitator has given you an IP address, open `http://:8000` in your browser. @@ -169,7 +169,7 @@ export const handler: Handler = (event, context) => { return { statusCode: 200, - body: "Hello World!" + body: "Hello World!", }; }; ``` From 7f85cafba4a2bd533cbe999bf79f1739b49e3ab2 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:53:38 +0000 Subject: [PATCH 22/35] Update README.md --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/README.md b/README.md index 9a9894e..bec45d7 100644 --- a/README.md +++ b/README.md @@ -183,3 +183,66 @@ Update `./nodejs/package.json` scripts: "build": "tsc" }, ``` + +--- + +## 3. The runtime + +**Goal:** Make the container act like a real Lambda server. + +### Add `.dockerignore` + +Create `./nodejs/.dockerignore` (inside the subdirectory). +This is critical because our build context is now that specific folder. + +```plaintext +build +node_modules +``` + +### Update `Dockerfile` + +Update `./nodejs/Dockerfile`. +Notice that the `COPY` paths are cleaner now because they are relative to the `nodejs` folder. + +```Dockerfile +FROM public.ecr.aws/lambda/nodejs:24 + +HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ + CMD [ "curl", "-I", "http://localhost:8080" ] + +COPY ./package*.json ./ + +RUN npm ci + +COPY ./ ./ + +RUN npm run build + +CMD [ "build/index.handler" ] +``` + +### Add cURL service + +Update `docker-compose.yaml` (in the root) to include a service that triggers our Lambda. + +```yaml +services: + curl: + image: curlimages/curl + depends_on: + lambda: + condition: service_healthy + command: + - -s + - -d {} + - http://lambda:8080/2015-03-31/functions/function/invocations + lambda: + build: ./nodejs +``` + +### Run the stack + +```shell +docker compose up --build --abort-on-container-exit +``` From eb9c48cccdd43478394dcb7c9b2925387b1fb9c0 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:56:49 +0000 Subject: [PATCH 23/35] Update README.md --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index bec45d7..e723253 100644 --- a/README.md +++ b/README.md @@ -246,3 +246,54 @@ services: ```shell docker compose up --build --abort-on-container-exit ``` + +--- + +## 4: Developer experience + +**Goal:** Simulate real-world events and environments. + +### Add environment variables + +Update `./nodejs/Dockerfile`: + +```Dockerfile +ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 +ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 +ENV AWS_LAMBDA_LOG_FORMAT=JSON +``` + +### Create an event file + +Create `./events/test.json` in the root (keep events outside the code folder): + +```json +{ + "user": "Alice", + "action": "login" +} +``` + +### Inject the event + +Update `docker-compose.yaml`: + +```yaml +curl: + # ... existing config + command: + - -s + - -d + - ${LAMBDA_INPUT:-{}} + - http://lambda:8080/2015-03-31/functions/function/invocations + volumes: + - ./events:/events:ro +``` + +### Test with data + +```shell +LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-container-exit +``` + +--- From f90fbd17075f4e2adf0617e9af468aed9a3c1d85 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:57:55 +0000 Subject: [PATCH 24/35] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e723253..eeb85af 100644 --- a/README.md +++ b/README.md @@ -280,10 +280,10 @@ Update `docker-compose.yaml`: ```yaml curl: - # ... existing config + # ... existing config command: - -s - - -d + - -d - ${LAMBDA_INPUT:-{}} - http://lambda:8080/2015-03-31/functions/function/invocations volumes: From 824cb871cc8c8dedd1151308b6dd35c4ba9de1e2 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 20:58:28 +0000 Subject: [PATCH 25/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eeb85af..beeb1fa 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Create `./nodejs/tsconfig.json` locally: ### Create the handler -Create `nodejs/src/index.ts`: +Create `./nodejs/src/index.ts`: ```typescript import { Handler } from "aws-lambda"; From 6e7eb5cf6a6b953dc8289f1c5bfdbed676a047d8 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 21:02:21 +0000 Subject: [PATCH 26/35] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index beeb1fa..5e04236 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ ## 🛑 Prerequisites +### General/global prerequisites + Before beginning this workshop, please ensure your environment is correctly set up by following the instructions in our prerequisites documentation: ➡️ **[Prerequisites guide](https://github.com/daemon-labs-resources/prerequisites)** @@ -241,7 +243,7 @@ services: build: ./nodejs ``` -### Run the stack +### Run the stack ```shell docker compose up --build --abort-on-container-exit @@ -253,7 +255,7 @@ docker compose up --build --abort-on-container-exit **Goal:** Simulate real-world events and environments. -### Add environment variables +### Add environment variables Update `./nodejs/Dockerfile`: From b56c20cad5eb10a0b0a20219948a0b5208f15aa1 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 21:06:55 +0000 Subject: [PATCH 27/35] Update README.md --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e04236..d6bfd57 100644 --- a/README.md +++ b/README.md @@ -213,11 +213,11 @@ FROM public.ecr.aws/lambda/nodejs:24 HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ CMD [ "curl", "-I", "http://localhost:8080" ] -COPY ./package*.json ./ +COPY ./package*.json ${LAMBDA_TASK_ROOT} RUN npm ci -COPY ./ ./ +COPY ./ ${LAMBDA_TASK_ROOT} RUN npm run build @@ -299,3 +299,71 @@ LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-container-e ``` --- + +## 5. Optimisation & versatility + +**Goal:** Prepare for production and demonstrate language flexibility. + +### Multi-stage build + +Replace `./nodejs/Dockerfile` with this optimised version: + +```Dockerfile +FROM FROM public.ecr.aws/lambda/nodejs:24 AS base + +FROM base AS builder + +COPY ./package*.json ${LAMBDA_TASK_ROOT} + +RUN npm ci + +COPY ./ ${LAMBDA_TASK_ROOT} + +RUN npm run build + +FROM base + +ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 +ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 +ENV AWS_LAMBDA_LOG_FORMAT=JSON + +HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ + CMD [ "curl", "-I", "http://localhost:8080" ] + +COPY --from=builder ${LAMBDA_TASK_ROOT}/package*.json ${LAMBDA_TASK_ROOT} + +RUN npm ci --only=production + +COPY --from=builder ${LAMBDA_TASK_ROOT}/build ${LAMBDA_TASK_ROOT}/build + +CMD [ "build/index.handler" ] +``` + +### Bonus: Python swap + +Create `./python/app.py`: + +```python +def handler(event, context): + return "Hello World!" +``` + +Create `./python/Dockerfile`: + +```Dockerfile +FROM public.ecr.aws/lambda/python:3.14 + +COPY ./ ${LAMBDA_TASK_ROOT} + +CMD [ "app.handler" ] +``` + +Update `docker-compose.yaml` to swap the build context: + +```yaml +services: + lambda: + build: ./python +``` + +--- From f80aa15277a2515f210b1dda838677b4f5ec4e85 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 21:09:53 +0000 Subject: [PATCH 28/35] Update README.md --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index d6bfd57..f223b64 100644 --- a/README.md +++ b/README.md @@ -367,3 +367,51 @@ services: ``` --- + +## 6: Advanced integration + +**Goal:** Connect to LocalStack. + +### Add LocalStack Service to `./docker-compose.yaml` + +```yaml +localstack: + image: localstack/localstack + ports: + - 4566:4566 + environment: + - SERVICES=s3 +``` + +### Update Lambda config + +Update `docker-compose.yaml`: + +```yaml +depends_on: + - localstack +environment: + - AWS_ENDPOINT_URL=http://localstack:4566 + - AWS_ACCESS_KEY_ID=test + - AWS_SECRET_ACCESS_KEY=test + - AWS_REGION=us-east-1 +``` + +### Update code + +Run `npm install @aws-sdk/client-s3` inside `./nodejs`. +Update `./nodejs/src/index.ts` with the S3 client logic. + +### Final run + +Run the following command: + +```shell +docker compose up --build --abort-on-container-exit +``` + +--- + +## 🎉 Congratulations + +You have built a clean, modular, serverless development environment. From a89468b725a7774157e999be055ad14b6dfd6200 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 21:12:50 +0000 Subject: [PATCH 29/35] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f223b64..93e838a 100644 --- a/README.md +++ b/README.md @@ -376,11 +376,11 @@ services: ```yaml localstack: - image: localstack/localstack - ports: - - 4566:4566 - environment: - - SERVICES=s3 + image: localstack/localstack + ports: + - 4566:4566 + environment: + - SERVICES=s3 ``` ### Update Lambda config @@ -399,7 +399,7 @@ environment: ### Update code -Run `npm install @aws-sdk/client-s3` inside `./nodejs`. +Run `npm install @aws-sdk/client-s3` inside `./nodejs`. Update `./nodejs/src/index.ts` with the S3 client logic. ### Final run From 350e430feda6b16604b4e59326c9f88e62c9e5b4 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 21:19:35 +0000 Subject: [PATCH 30/35] Update README.md --- README.md | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 93e838a..41d600f 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,7 @@ LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-container-e Replace `./nodejs/Dockerfile` with this optimised version: ```Dockerfile -FROM FROM public.ecr.aws/lambda/nodejs:24 AS base +FROM public.ecr.aws/lambda/nodejs:24 AS base FROM base AS builder @@ -399,8 +399,44 @@ environment: ### Update code -Run `npm install @aws-sdk/client-s3` inside `./nodejs`. -Update `./nodejs/src/index.ts` with the S3 client logic. +First, install the SDK inside `./nodejs` (or on your host machine if you have Node installed): + +```shell +npm install @aws-sdk/client-s3 +``` + +Next, update `./nodejs/src/index.ts` with the S3 client logic: + +```typescript +import { Handler } from "aws-lambda"; +import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3"; + +const client = new S3Client({ + endpoint: process.env.AWS_ENDPOINT_URL, // Points to LocalStack + forcePathStyle: true, // Required for local mocking + region: process.env.AWS_REGION +}); + +export const handler: Handler = async (event, context) => { + try { + const command = new ListBucketsCommand({}); + const response = await client.send(command); + + console.log("S3 Buckets:", response.Buckets); + + return { + statusCode: 200, + body: JSON.stringify(response.Buckets || []), + }; + } catch (error) { + console.error(error); + return { + statusCode: 500, + body: "Error connecting to S3" + } + } +}; +``` ### Final run From bf8c1cc1c7e4746302a36d1628d22143624d4a1e Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 21:22:00 +0000 Subject: [PATCH 31/35] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 41d600f..f2378d2 100644 --- a/README.md +++ b/README.md @@ -413,8 +413,8 @@ import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3"; const client = new S3Client({ endpoint: process.env.AWS_ENDPOINT_URL, // Points to LocalStack - forcePathStyle: true, // Required for local mocking - region: process.env.AWS_REGION + forcePathStyle: true, // Required for local mocking + region: process.env.AWS_REGION, }); export const handler: Handler = async (event, context) => { @@ -431,9 +431,9 @@ export const handler: Handler = async (event, context) => { } catch (error) { console.error(error); return { - statusCode: 500, - body: "Error connecting to S3" - } + statusCode: 500, + body: "Error connecting to S3", + }; } }; ``` From 9673e004346ab27632f8313c30fe6e62459cc228 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Tue, 25 Nov 2025 21:24:20 +0000 Subject: [PATCH 32/35] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f2378d2..aa441b8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Learn to build and develop AWS Lambda functions locally with Docker -@todo Workshop description. +This guide walks you through setting up a robust local Serverless development environment using **Docker**, **AWS Lambda**, **TypeScript**, and **LocalStack**. +It focuses on emulating the cloud runtime entirely offline, optimising production images with multi-stage builds, and mocking external services like S3 to create a complete, cost-free development workflow. --- From d8fd8c4d17d2c3fe10c4fa1587abac3ee1021290 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Wed, 26 Nov 2025 10:48:10 +0000 Subject: [PATCH 33/35] Update README.md --- README.md | 101 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index aa441b8..5378bfe 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,13 @@ Before beginning this workshop, please ensure your environment is correctly set ➡️ **[Prerequisites guide](https://github.com/daemon-labs-resources/prerequisites)** -### Retrieve Docker images - -#### Load Docker images +### Load Docker images > [!CAUTION] > This only works when attending a workshop in person. > Due to having a number of people trying to retrieve Docker images at the same time, this allows for a more efficient way. > -> If you are **NOT** in an in-person workshop, see [pull docker images](#pull-docker-images). +> If you are **NOT** in an in-person workshop, continue to the [workshop](#1-the-foundation), Docker images will be pulled as needed. Once the facilitator has given you an IP address, open `http://:8000` in your browser. @@ -36,22 +34,6 @@ Run the following command: docker load -i ~/Downloads/workshop-images.tar ``` -#### Pull Docker images - -> [!CAUTION] -> Only use this approach if you are running through this workshop on your own. -> -> If you are in an in-person workshop, see [load docker images](#load-docker-images). - -Run the following command: - -```shell -docker pull public.ecr.aws/lambda/nodejs:24 -docker pull public.ecr.aws/lambda/python:3.14 -docker pull curlimages/curl -docker pull localstack/localstack -``` - ### Validate Docker images Run the following command: @@ -301,9 +283,9 @@ LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-container-e --- -## 5. Optimisation & versatility +## 5. Optimisation -**Goal:** Prepare for production and demonstrate language flexibility. +**Goal:** Prepare for production with multi-stage builds. ### Multi-stage build @@ -340,31 +322,12 @@ COPY --from=builder ${LAMBDA_TASK_ROOT}/build ${LAMBDA_TASK_ROOT}/build CMD [ "build/index.handler" ] ``` -### Bonus: Python swap - -Create `./python/app.py`: - -```python -def handler(event, context): - return "Hello World!" -``` - -Create `./python/Dockerfile`: +### Test the optimised build -```Dockerfile -FROM public.ecr.aws/lambda/python:3.14 +Run the following command to ensure everything still works: -COPY ./ ${LAMBDA_TASK_ROOT} - -CMD [ "app.handler" ] -``` - -Update `docker-compose.yaml` to swap the build context: - -```yaml -services: - lambda: - build: ./python +```shell +docker compose up --build --abort-on-container-exit ``` --- @@ -449,6 +412,54 @@ docker compose up --build --abort-on-container-exit --- +## 7. Bonus: Swapping runtimes + +\*_Goal:_ Demonstrate the versatility of Docker by swapping to Python. + +One of the biggest advantages of developing Lambdas with Docker is that the infrastructure pattern remains exactly the same, regardless of the language you use. + +### Create a Python `Dockerfile` + +Create `./python/Dockerfile` with the following content: + +```Dockerfile +FROM public.ecr.aws/lambda/python:3.14 + +COPY ./ ${LAMBDA_TASK_ROOT} + +CMD [ "app.handler" ] +``` + +### Create the Python handler + +Create the handler file at `./python/app.py`: + +```python +def handler(event, context): + return "Hello World!" +``` + +### Update `docker-compose.yaml` + +Update the `lambda` service in `docker-compose.yaml` to point to the Python folder: + +```yaml +services: + lambda: + build: ./python +``` + +### Run it + +```shell +docker compose up --build --abort-on-container-exit +``` + +> [!NOTE] +> You will see the build process switch to pulling the Python base image, but the curl command and event injection work exactly the same way. + +--- + ## 🎉 Congratulations You have built a clean, modular, serverless development environment. From 29d34d55f3fc2e2b27041daa4d0ed7f25c9f0d31 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Wed, 26 Nov 2025 13:13:25 +0000 Subject: [PATCH 34/35] Update README.md --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5378bfe..e8b0cc6 100644 --- a/README.md +++ b/README.md @@ -240,17 +240,30 @@ docker compose up --build --abort-on-container-exit ### Add environment variables -Update `./nodejs/Dockerfile`: +Update `./docker-compose.yaml`: -```Dockerfile -ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 -ENV AWS_LAMBDA_LOG_FORMAT=JSON +```yaml +services: + # ... existing config + lambda: + build: ./nodejs + environment: + AWS_LAMBDA_FUNCTION_MEMORY_SIZE: 128 + AWS_LAMBDA_FUNCTION_TIMEOUT: 3 + AWS_LAMBDA_LOG_FORMAT: JSON +``` + +### Create the events subdirectory + +Create `./events` in the root (keep events outside the code folder): + +```shell +mkdir ./events ``` -### Create an event file +### Create a custom event file -Create `./events/test.json` in the root (keep events outside the code folder): +Create `./events/custom.json`: ```json { @@ -259,26 +272,43 @@ Create `./events/test.json` in the root (keep events outside the code folder): } ``` +### Create API Gateway event file + +Create `./events/api-gateway.json`: + +```json +{ + "resource": "/", + "path": "/", + "httpMethod": "POST", + "body": "{\"user\": \"Alice\"}", + "isBase64Encoded": false +} +``` + ### Inject the event Update `docker-compose.yaml`: ```yaml -curl: - # ... existing config - command: - - -s - - -d - - ${LAMBDA_INPUT:-{}} - - http://lambda:8080/2015-03-31/functions/function/invocations - volumes: +services: + curl: + # ... existing config + command: + - -s + - -d + - ${LAMBDA_INPUT:-{}} + - http://lambda:8080/2015-03-31/functions/function/invocations +volumes: - ./events:/events:ro + # ... existing config ``` ### Test with data ```shell -LAMBDA_INPUT=@/events/test.json docker compose up --build --abort-on-container-exit +LAMBDA_INPUT=@/events/custom.json docker compose up --build --abort-on-container-exit +LAMBDA_INPUT=@/events/api-gateway.json docker compose up --build --abort-on-container-exit ``` --- From 6a592fd18d1721b8c59cde6363746f219d1cde73 Mon Sep 17 00:00:00 2001 From: Gary Rutland Date: Wed, 26 Nov 2025 14:19:25 +0000 Subject: [PATCH 35/35] Update README.md --- README.md | 128 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index e8b0cc6..1424365 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,12 @@ mkdir -p ~/Documents/daemon-labs/docker-aws-lambda We keep our application code separate from infrastructure config. ```shell -mkdir nodejs +mkdir ./nodejs ``` ### Create the `Dockerfile` -Create the file at `./nodejs/Dockerfile` (inside the subdirectory). +Create the file at `nodejs/Dockerfile` (inside the subdirectory). ```Dockerfile FROM public.ecr.aws/lambda/nodejs:24 @@ -96,12 +96,20 @@ services: ### Initialise the container -Run this command to start an interactive shell. +Run this command to start an interactive shell: ```shell docker compose run -it --rm --entrypoint /bin/sh -v ./nodejs:/var/task lambda ``` +### Image check + +Run the following command: + +```shell +node --version +``` + --- ## 2. The application @@ -130,7 +138,7 @@ exit ### Configure TypeScript -Create `./nodejs/tsconfig.json` locally: +Create `nodejs/tsconfig.json` locally: ```json { @@ -143,12 +151,12 @@ Create `./nodejs/tsconfig.json` locally: ### Create the handler -Create `./nodejs/src/index.ts`: +Create `nodejs/src/index.ts`: ```typescript import { Handler } from "aws-lambda"; -export const handler: Handler = (event, context) => { +export const handler: Handler = async (event, context) => { console.log("Hello world!"); console.log({ event, context }); @@ -161,12 +169,10 @@ export const handler: Handler = (event, context) => { ### Add build script -Update `./nodejs/package.json` scripts: +Update `nodejs/package.json` scripts: ```json -"scripts": { - "build": "tsc" -}, +"build": "tsc" ``` --- @@ -177,7 +183,7 @@ Update `./nodejs/package.json` scripts: ### Add `.dockerignore` -Create `./nodejs/.dockerignore` (inside the subdirectory). +Create `nodejs/.dockerignore` (inside the subdirectory). This is critical because our build context is now that specific folder. ```plaintext @@ -187,15 +193,11 @@ node_modules ### Update `Dockerfile` -Update `./nodejs/Dockerfile`. -Notice that the `COPY` paths are cleaner now because they are relative to the `nodejs` folder. +Update `nodejs/Dockerfile`: ```Dockerfile FROM public.ecr.aws/lambda/nodejs:24 -HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ - CMD [ "curl", "-I", "http://localhost:8080" ] - COPY ./package*.json ${LAMBDA_TASK_ROOT} RUN npm ci @@ -207,6 +209,21 @@ RUN npm run build CMD [ "build/index.handler" ] ``` +### Update Lambda healthcheck + +Update `docker-compose.yaml`: + +```yaml +lambda: + build: ./nodejs + healthcheck: + test: + - CMD + - curl + - -I + - http://localhost:8080 +``` + ### Add cURL service Update `docker-compose.yaml` (in the root) to include a service that triggers our Lambda. @@ -240,13 +257,13 @@ docker compose up --build --abort-on-container-exit ### Add environment variables -Update `./docker-compose.yaml`: +Update `docker-compose.yaml`: ```yaml services: # ... existing config lambda: - build: ./nodejs + # ... existing config environment: AWS_LAMBDA_FUNCTION_MEMORY_SIZE: 128 AWS_LAMBDA_FUNCTION_TIMEOUT: 3 @@ -255,7 +272,7 @@ services: ### Create the events subdirectory -Create `./events` in the root (keep events outside the code folder): +Create the events subdirectory in the root (keep events outside the code folder): ```shell mkdir ./events @@ -263,7 +280,7 @@ mkdir ./events ### Create a custom event file -Create `./events/custom.json`: +Create `events/custom.json`: ```json { @@ -274,7 +291,7 @@ Create `./events/custom.json`: ### Create API Gateway event file -Create `./events/api-gateway.json`: +Create `events/api-gateway.json`: ```json { @@ -300,14 +317,21 @@ services: - ${LAMBDA_INPUT:-{}} - http://lambda:8080/2015-03-31/functions/function/invocations volumes: - - ./events:/events:ro + - ./events:/events:ro # ... existing config ``` ### Test with data +```shell +docker compose up --build --abort-on-container-exit +``` + ```shell LAMBDA_INPUT=@/events/custom.json docker compose up --build --abort-on-container-exit +``` + +```shell LAMBDA_INPUT=@/events/api-gateway.json docker compose up --build --abort-on-container-exit ``` @@ -319,7 +343,7 @@ LAMBDA_INPUT=@/events/api-gateway.json docker compose up --build --abort-on-cont ### Multi-stage build -Replace `./nodejs/Dockerfile` with this optimised version: +Replace `nodejs/Dockerfile` with this optimised version: ```Dockerfile FROM public.ecr.aws/lambda/nodejs:24 AS base @@ -336,13 +360,6 @@ RUN npm run build FROM base -ENV AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -ENV AWS_LAMBDA_FUNCTION_TIMEOUT=3 -ENV AWS_LAMBDA_LOG_FORMAT=JSON - -HEALTHCHECK --interval=1s --timeout=1s --retries=30 \ - CMD [ "curl", "-I", "http://localhost:8080" ] - COPY --from=builder ${LAMBDA_TASK_ROOT}/package*.json ${LAMBDA_TASK_ROOT} RUN npm ci --only=production @@ -366,15 +383,20 @@ docker compose up --build --abort-on-container-exit **Goal:** Connect to LocalStack. -### Add LocalStack Service to `./docker-compose.yaml` +### Add LocalStack Service to `docker-compose.yaml` ```yaml localstack: image: localstack/localstack - ports: - - 4566:4566 - environment: - - SERVICES=s3 + healthcheck: + test: + - CMD + - curl + - -f + - http://localhost:4566/_localstack/health + interval: 1s + timeout: 1s + retries: 30 ``` ### Update Lambda config @@ -383,23 +405,39 @@ Update `docker-compose.yaml`: ```yaml depends_on: - - localstack + localstack: + condition: service_healthy environment: - - AWS_ENDPOINT_URL=http://localstack:4566 - - AWS_ACCESS_KEY_ID=test - - AWS_SECRET_ACCESS_KEY=test - - AWS_REGION=us-east-1 + AWS_LAMBDA_FUNCTION_MEMORY_SIZE: 128 + AWS_LAMBDA_FUNCTION_TIMEOUT: 3 + AWS_LAMBDA_LOG_FORMAT: JSON + AWS_ENDPOINT_URL: http://localstack:4566 + AWS_SECRET_ACCESS_KEY: test + AWS_ACCESS_KEY_ID: test + AWS_REGION: us-east-1 ``` ### Update code -First, install the SDK inside `./nodejs` (or on your host machine if you have Node installed): +Run this command to start an interactive shell: + +```shell +docker compose run -it --rm --no-deps --entrypoint /bin/sh -v ./nodejs:/var/task lambda +``` + +Install the SDK: ```shell npm install @aws-sdk/client-s3 ``` -Next, update `./nodejs/src/index.ts` with the S3 client logic: +Exit the container + +```shell +exit +``` + +Next, update `nodejs/src/index.ts` with the S3 client logic: ```typescript import { Handler } from "aws-lambda"; @@ -444,13 +482,13 @@ docker compose up --build --abort-on-container-exit ## 7. Bonus: Swapping runtimes -\*_Goal:_ Demonstrate the versatility of Docker by swapping to Python. +**Goal:** Demonstrate the versatility of Docker by swapping to Python. One of the biggest advantages of developing Lambdas with Docker is that the infrastructure pattern remains exactly the same, regardless of the language you use. ### Create a Python `Dockerfile` -Create `./python/Dockerfile` with the following content: +Create `python/Dockerfile` with the following content: ```Dockerfile FROM public.ecr.aws/lambda/python:3.14 @@ -462,7 +500,7 @@ CMD [ "app.handler" ] ### Create the Python handler -Create the handler file at `./python/app.py`: +Create the handler file at `python/app.py`: ```python def handler(event, context):