diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3ccb66f..4603319 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -17,7 +17,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v5
       with:
-        go-version: '1.20'
+        go-version: '1.22'
         
     - name: Build
       env:
@@ -29,7 +29,7 @@ jobs:
         name: aws-lambda-rie
         path: bin/*
     - name: Release binaries
-      uses: softprops/action-gh-release@v1
+      uses: softprops/action-gh-release@v2
       if: startsWith(github.ref, 'refs/tags/')
       with:
         files: bin/*
diff --git a/.github/workflows/check-binaries.yml b/.github/workflows/check-binaries.yml
new file mode 100644
index 0000000..75fa28f
--- /dev/null
+++ b/.github/workflows/check-binaries.yml
@@ -0,0 +1,82 @@
+name: Check binaries
+
+on: 
+  workflow_dispatch:
+  schedule:
+   - cron: "0 16 * * 1-5"  # min h d Mo DoW / 9am PST M-F
+
+jobs:
+  check-for-vulnerabilities:
+    runs-on: ubuntu-latest
+    outputs:
+      report_contents: ${{ steps.save-output.outputs.report_contents }}
+    steps:
+      - name: Setup python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.11'
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          ref: main
+      - name: Download latest release
+        uses: robinraju/release-downloader@v1.10
+        with:
+          latest: true
+          fileName: 'aws-lambda-rie*'
+          out-file-path: "bin"
+      - name: Run check for vulnerabilities
+        id: check-binaries
+        run: |
+          make check-binaries
+      - if: always() && failure()   # `always()` to run even if the previous step failed. Failure means that there are vulnerabilities
+        name: Save content of the vulnerabilities report as GitHub output
+        id: save-output
+        run: |
+          report_csv="$(ls -tr output.cve-bin-*.csv 2>/dev/null | tail -n1)"   # last file generated
+          if [ -z "$report_csv" ]; then
+            echo "No file with vulnerabilities. Probably a failure in previous step."
+          else
+            echo "Vulnerabilities stored in $report_csv"
+          fi
+          final_report="${report_csv}.txt"
+          awk -F',' '{n=split($10, path, "/"); print $2,$3,$4,$5,path[n]}' "$report_csv" | column -t > "$final_report"   # make the CSV nicer
+          echo "report_contents<<EOF" >> "$GITHUB_OUTPUT"
+          cat "$final_report"         >> "$GITHUB_OUTPUT"
+          echo "EOF"                  >> "$GITHUB_OUTPUT"
+      - if: always() && steps.save-output.outputs.report_contents
+        name: Build new binaries and check vulnerabilities again
+        id: check-new-version
+        run: |
+          mkdir ./bin2
+          mv ./bin/* ./bin2
+          make compile-with-docker-all
+          latest_version=$(strings bin/aws-lambda-rie* | grep '^go1\.' | sort | uniq)
+          echo "latest_version=$latest_version" >> "$GITHUB_OUTPUT"
+          make check-binaries
+      - if: always() && steps.save-output.outputs.report_contents
+        name: Save outputs for the check with the latest build
+        id: save-new-version
+        run: |
+          if [ "${{ steps.check-new-version.outcome }}" == "failure" ]; then
+            fixed="No"
+          else
+            fixed="Yes"
+          fi
+          echo "fixed=$fixed" >> "$GITHUB_OUTPUT"
+      - if: always() && steps.save-output.outputs.report_contents
+        name: Create GitHub Issue indicating vulnerabilities
+        id: create-issue
+        uses: dacbd/create-issue-action@main
+        with:
+          token: ${{ github.token }}
+          title: |
+            CVEs found in latest RIE release
+          body: |
+            ###  CVEs found in latest RIE release
+            ```
+            ${{ steps.save-output.outputs.report_contents }}
+            ```
+            
+            #### Are these resolved by building with the latest patch version of Go (${{ steps.check-new-version.outputs.latest_version }})?:
+            > **${{ steps.save-new-version.outputs.fixed }}**
diff --git a/.github/workflows/integ-tests.yml b/.github/workflows/integ-tests.yml
new file mode 100644
index 0000000..7fddc95
--- /dev/null
+++ b/.github/workflows/integ-tests.yml
@@ -0,0 +1,49 @@
+name: Run Integration Tests
+
+on:
+  pull_request:
+    branches:
+      - develop
+
+jobs:
+  go-tests:
+    runs-on: ubuntu-latest
+    environment:
+      name: integ-tests
+    steps:
+        - uses: actions/checkout@v4
+        - name: run go tests
+          run: make tests-with-docker
+  integ-tests-x86:
+    runs-on: ubuntu-latest
+    environment:
+      name: integ-tests
+    steps:
+        - uses: actions/checkout@v4
+        - uses: actions/setup-python@v5
+          with:
+            python-version: '3.11'
+        - name: run integration tests
+          run: make integ-tests-with-docker-x86-64
+  integ-tests-arm64:
+    runs-on: ubuntu-latest
+    environment:
+      name: integ-tests
+    steps:
+        - uses: actions/checkout@v4
+        - uses: actions/setup-python@v5
+          with:
+            python-version: '3.11'
+        - name: run integration tests
+          run: make integ-tests-with-docker-arm64
+  integ-tests-old:
+    runs-on: ubuntu-latest
+    environment:
+      name: integ-tests
+    steps:
+        - uses: actions/checkout@v4
+        - uses: actions/setup-python@v5
+          with:
+            python-version: '3.11'
+        - name: run integration tests
+          run: make integ-tests-with-docker-old
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..32e878d
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,41 @@
+name: Release
+
+on:
+  workflow_dispatch:
+    inputs:
+      releaseVersion:
+        description: "Version to use for the release."
+        required: true
+        default: "X.Y"
+      releaseBody:
+        description: "Information about the release"
+        required: true
+        default: "New release"
+jobs:
+  Release:
+    environment: Release
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          ref: main
+      - name: Set up python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.11'
+      - name: Build
+        run: make compile-with-docker-all
+      - name: Run Integ Tests
+        run: |
+          make tests-with-docker
+          make integ-tests
+      - name: Release
+        uses: softprops/action-gh-release@v2
+        with:
+          name: Release ${{ github.event.inputs.releaseVersion }}
+          tag_name: v${{ github.event.inputs.releaseVersion }}
+          body: ${{ github.event.inputs.releaseBody }}
+          files: |
+            bin/aws-lambda-rie
+            bin/aws-lambda-rie-arm64
+            bin/aws-lambda-rie-x86_64
diff --git a/Makefile b/Makefile
index 85bebb2..27d6442 100644
--- a/Makefile
+++ b/Makefile
@@ -12,20 +12,24 @@ GO_ARCH_arm64 := arm64
 DESTINATION_x86_64 := bin/${BINARY_NAME}-x86_64
 DESTINATION_arm64 := bin/${BINARY_NAME}-arm64
 
+run_in_docker = docker run --env GOPROXY=direct -v $(shell pwd):/LambdaRuntimeLocal -w /LambdaRuntimeLocal golang:1.22 $(1)
+
 compile-with-docker-all:
-	make ARCH=x86_64 compile-with-docker
-	make ARCH=arm64 compile-with-docker
+	$(call run_in_docker, make compile-lambda-linux-all)
 
 compile-lambda-linux-all:
 	make ARCH=x86_64 compile-lambda-linux
 	make ARCH=arm64 compile-lambda-linux
 
 compile-with-docker:
-	docker run --rm --env GOPROXY=direct -v $(shell pwd):/LambdaRuntimeLocal -w /LambdaRuntimeLocal golang:1.20 make ARCH=${ARCH} compile-lambda-linux
+	$(call run_in_docker, make ARCH=${ARCH} compile-lambda-linux)
 
 compile-lambda-linux:
 	CGO_ENABLED=0 GOOS=linux GOARCH=${GO_ARCH_${ARCH}} go build -buildvcs=false -ldflags "${RELEASE_BUILD_LINKER_FLAGS}" -gcflags="${GC_FLAGS}" -o ${DESTINATION_${ARCH}} ./cmd/localstack
 
+tests-with-docker:
+	$(call run_in_docker, make tests)
+
 tests:
 	go test ./...
 
@@ -33,12 +37,41 @@ integ-tests-and-compile: tests
 	make compile-lambda-linux-all
 	make integ-tests
 
-integ-tests-with-docker: tests 
+integ-tests-with-docker: tests-with-docker
 	make compile-with-docker-all
 	make integ-tests
-	
-integ-tests:
+
+prep-python:
 	python3 -m venv .venv
 	.venv/bin/pip install --upgrade pip
 	.venv/bin/pip install requests parameterized
+
+exec-python-e2e-test:
 	.venv/bin/python3 test/integration/local_lambda/test_end_to_end.py
+
+integ-tests:
+	make prep-python
+	docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
+	make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test
+	make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test
+	make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test
+
+integ-tests-with-docker-x86-64:
+	make ARCH=x86_64 compile-with-docker
+	make prep-python
+	make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test
+
+integ-tests-with-docker-arm64:
+	make ARCH=arm64 compile-with-docker
+	make prep-python
+	docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
+	make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test
+
+integ-tests-with-docker-old:
+	make ARCH=old compile-with-docker
+	make prep-python
+	make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test
+
+check-binaries: prep-python
+	.venv/bin/pip install cve-bin-tool
+	.venv/bin/python -m cve_bin_tool.cli bin/ -r go -d REDHAT,OSV,GAD,CURL --no-0-cve-report -f csv
diff --git a/cmd/aws-lambda-rie/handlers.go b/cmd/aws-lambda-rie/handlers.go
index 42032cf..2cca12d 100644
--- a/cmd/aws-lambda-rie/handlers.go
+++ b/cmd/aws-lambda-rie/handlers.go
@@ -5,6 +5,7 @@ package main
 
 import (
 	"bytes"
+	"encoding/base64"
 	"fmt"
 	"io/ioutil"
 	"math"
@@ -81,6 +82,13 @@ func InvokeHandler(w http.ResponseWriter, r *http.Request, sandbox Sandbox, bs i
 		return
 	}
 
+	rawClientContext, err := base64.StdEncoding.DecodeString(r.Header.Get("X-Amz-Client-Context"))
+	if err != nil {
+		log.Errorf("Failed to decode X-Amz-Client-Context: %s", err)
+		w.WriteHeader(500)
+		return
+	}
+
 	initDuration := ""
 	inv := GetenvWithDefault("AWS_LAMBDA_FUNCTION_TIMEOUT", "300")
 	timeoutDuration, _ := time.ParseDuration(inv + "s")
@@ -114,6 +122,7 @@ func InvokeHandler(w http.ResponseWriter, r *http.Request, sandbox Sandbox, bs i
 		TraceID:            r.Header.Get("X-Amzn-Trace-Id"),
 		LambdaSegmentID:    r.Header.Get("X-Amzn-Segment-Id"),
 		Payload:            bytes.NewReader(bodyBytes),
+		ClientContext:      string(rawClientContext),
 	}
 	fmt.Println("START RequestId: " + invokePayload.ID + " Version: " + functionVersion)
 
diff --git a/go.mod b/go.mod
index 992860c..fe41c9a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,20 +1,20 @@
 module go.amzn.com
 
-go 1.20
+go 1.22
 
 require (
-	github.com/aws/aws-lambda-go v1.41.0
+	github.com/aws/aws-lambda-go v1.46.0
 	github.com/aws/aws-sdk-go v1.44.62
 	github.com/aws/aws-xray-daemon v0.0.0-20230202010956-acaf06e9a638
 	github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575
 	github.com/fsnotify/fsnotify v1.6.0
-	github.com/go-chi/chi v4.1.2+incompatible
-	github.com/google/uuid v1.3.0
+	github.com/go-chi/chi v1.5.5
+	github.com/google/uuid v1.6.0
 	github.com/jessevdk/go-flags v1.5.0
 	github.com/shirou/gopsutil v2.19.10+incompatible
 	github.com/sirupsen/logrus v1.9.3
-	github.com/stretchr/testify v1.8.4
-	golang.org/x/sync v0.2.0
+	github.com/stretchr/testify v1.9.0
+	golang.org/x/sync v0.6.0
 	golang.org/x/sys v0.14.0
 )
 
@@ -24,7 +24,7 @@ require (
 	github.com/go-ole/go-ole v1.2.4 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/stretchr/objx v0.5.0 // indirect
+	github.com/stretchr/objx v0.5.2 // indirect
 	golang.org/x/net v0.18.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	gopkg.in/yaml.v2 v2.2.8 // indirect
diff --git a/go.sum b/go.sum
index 0474547..5b5f27c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
-github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l+3Y=
-github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM=
+github.com/aws/aws-lambda-go v1.46.0 h1:UWVnvh2h2gecOlFhHQfIPQcD8pL/f7pVCutmFl+oXU8=
+github.com/aws/aws-lambda-go v1.46.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
 github.com/aws/aws-sdk-go v1.44.62 h1:N8qOPnBhl2ZCIFiqyB640Xt5CeX9D8CEVhG/Vj7jGJU=
 github.com/aws/aws-sdk-go v1.44.62/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
 github.com/aws/aws-xray-daemon v0.0.0-20230202010956-acaf06e9a638 h1:G0C87W0m2uyh3uHV24Q60JJx+AyJ3//gJjalvSizXhc=
@@ -13,12 +13,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
-github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
-github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
+github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
+github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
 github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@@ -33,19 +33,16 @@ github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMT
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
 golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
-golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
-golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/test/integration/local_lambda/test_end_to_end.py b/test/integration/local_lambda/test_end_to_end.py
index c5c3e63..8e34b77 100644
--- a/test/integration/local_lambda/test_end_to_end.py
+++ b/test/integration/local_lambda/test_end_to_end.py
@@ -4,283 +4,257 @@
 from subprocess import Popen, PIPE
 from unittest import TestCase, main
 from pathlib import Path
+import base64
+import json
 import time
-
+import os
 import requests
+from contextlib import contextmanager
 from parameterized import parameterized
 
-SLEEP_TIME = 2
+SLEEP_TIME = 1.5
 DEFAULT_1P_ENTRYPOINT = "/lambda-entrypoint.sh"
 ARCHS = ["x86_64", "arm64", ""]
 
 
+
 class TestEndToEnd(TestCase):
+    ARCH = os.environ.get('TEST_ARCH', "")
+    PORT = os.environ.get('TEST_PORT', 8002)
     @classmethod
     def setUpClass(cls):
         testdata_path = Path(__file__).resolve().parents[1].joinpath("testdata")
         dockerfile_path = testdata_path.joinpath("Dockerfile-allinone")
-        cls.image_name = "aws-lambda-local:testing"
         cls.path_to_binary = Path().resolve().joinpath("bin")
 
         # build image
-        for arch in ARCHS:
-            image_name = cls.image_name if arch == "" else f"{cls.image_name}-{arch}"
-            architecture = arch if arch == "arm64" else "amd64"
-            build_cmd = [
-                "docker",
-                "build",
-                "--platform",
-                f"linux/{architecture}",
-                "-t",
-                image_name,
-                "-f",
-                str(dockerfile_path),
-                str(testdata_path),
-            ]
-            Popen(build_cmd).communicate()
+        image_name_base = "aws-lambda-local:testing"
+        cls.image_name = image_name_base if cls.ARCH == "" else f"{image_name_base}-{cls.ARCH}"
+        architecture = cls.ARCH if cls.ARCH == "arm64" else "amd64"
+        docker_arch = cls.ARCH if cls.ARCH == "arm64" else "x86_64"
+        
+        build_cmd = [
+            "docker",
+            "build",
+            "--platform",
+            f"linux/{architecture}",
+            "-t",
+            cls.image_name,
+            "-f",
+            str(dockerfile_path),
+            str(testdata_path),
+            "--build-arg",
+            f"IMAGE_ARCH={docker_arch}",
+        ]
+        Popen(build_cmd).communicate()
 
     @classmethod
     def tearDownClass(cls):
-        images_to_delete = [
-            "envvarcheck",
-            "twoinvokes",
-            "arnexists",
-            "customname",
-            "timeout",
-            "exception",
-            "remaining_time_in_three_seconds",
-            "remaining_time_in_ten_seconds",
-            "remaining_time_in_default_deadline",
-            "pre-runtime-api",
-            "assert-overwritten",
-            "port_override"
-        ]
-
-        for image in images_to_delete:
-            for arch in ARCHS:
-                arch_tag = "" if arch == "" else f"-{arch}"
-                cmd = f"docker rm -f {image}{arch_tag}"
-                Popen(cmd.split(" ")).communicate()
-            
-        for arch in ARCHS:
-            arch_tag = "" if arch == "" else f"-{arch}"
-            Popen(f"docker rmi {cls.image_name}{arch_tag}".split(" ")).communicate()
-
-    def tagged_name(self, name, architecture):
-        tag = self.get_tag(architecture)
-        return (name + tag, "aws-lambda-rie" + tag, self.image_name + tag)
-
-    def get_tag(self, architecture):
-        return "" if architecture == "" else str(f"-{architecture}")
+        Popen(f"docker rmi {cls.image_name}".split(" ")).communicate()
 
-    @parameterized.expand([("x86_64", "8000"), ("arm64", "9000"), ("", "9050")])
-    def test_env_var_with_equal_sign(self, arch, port):
-        image, rie, image_name = self.tagged_name("envvarcheck", arch)
+    def tagged_name(self, name):
+        tag = self.get_tag()
+        return (name + tag, "aws-lambda-rie" + tag, self.image_name)
 
-        cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_env_var_handler"
+    def get_tag(self):
+        return "" if self.ARCH == "" else str(f"-{self.ARCH}")
+    
+    def run_command(self, cmd):
         Popen(cmd.split(" ")).communicate()
-
-        # sleep 1s to give enough time for the endpoint to be up to curl
+    
+    def sleep_1s(self):
         time.sleep(SLEEP_TIME)
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
+    def invoke_function(self, json={}, headers={}):
+        return requests.post(
+            f"http://localhost:{self.PORT}/2015-03-31/functions/function/invocations",
+            json=json,
+            headers=headers,
         )
-        self.assertEqual(b'"4=4"', r.content)
-
-    @parameterized.expand([("x86_64", "8001"), ("arm64", "9001"), ("", "9051")])
-    def test_two_invokes(self, arch, port):
-        image, rie, image_name = self.tagged_name("twoinvokes", arch)
-
-        cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler"
-
-        Popen(cmd.split(" ")).communicate()
-
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b'"My lambda ran succesfully"', r.content)
-
-        # Make sure we can invoke the function twice
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b'"My lambda ran succesfully"', r.content)
-
-    @parameterized.expand([("x86_64", "8002"), ("arm64", "9002"), ("", "9052")])
-    def test_lambda_function_arn_exists(self, arch, port):
-        image, rie, image_name = self.tagged_name("arnexists", arch)
-
-        cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context"
-
-        Popen(cmd.split(" ")).communicate()
-
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
-
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b'"My lambda ran succesfully"', r.content)
-
-    @parameterized.expand([("x86_64", "8003"), ("arm64", "9003"), ("", "9053")])
-    def test_lambda_function_arn_exists_with_defining_custom_name(self, arch, port):
-        image, rie, image_name = self.tagged_name("customname", arch)
-
-        cmd = f"docker run --name {image} --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context"
-        Popen(cmd.split(" ")).communicate()
-
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
+    @contextmanager
+    def create_container(self, param, image):
+        try:
+            platform = "x86_64" if self.ARCH == "" else self.ARCH
+            cmd_full = f"docker run --platform linux/{platform} {param}"
+            self.run_command(cmd_full)
+        
+            # sleep 1s to give enough time for the endpoint to be up to curl
+            self.sleep_1s()
+            yield
+        except Exception as e:
+            print(f"An error occurred while executing cmd: {cmd_full}. error: {e}")
+            raise e
+        finally:
+            self.run_command(f"docker stop {image}")
+            self.run_command(f"docker rm -f {image}")
+        
+
+    def test_env_var_with_equal_sign(self):
+        image, rie, image_name = self.tagged_name("envvarcheck")
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_env_var_handler"
+        
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b'"4=4"', r.content)
+
+
+    def test_two_invokes(self):
+        image, rie, image_name = self.tagged_name("twoinvokes")
+
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler"
+
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b'"My lambda ran succesfully"', r.content)
+
+            # Make sure we can invoke the function twice
+            r = self.invoke_function()
+            
+            self.assertEqual(b'"My lambda ran succesfully"', r.content)
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b'"My lambda ran succesfully"', r.content)
 
-    @parameterized.expand([("x86_64", "8004"), ("arm64", "9004"), ("", "9054")])
-    def test_timeout_invoke(self, arch, port):
-        image, rie, image_name = self.tagged_name("timeout", arch)
+    def test_lambda_function_arn_exists(self):
+        image, rie, image_name = self.tagged_name("arnexists")
 
-        cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=1 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.sleep_handler"
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context"
 
-        Popen(cmd.split(" ")).communicate()
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b'"My lambda ran succesfully"', r.content)
 
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b"Task timed out after 1.00 seconds", r.content)
+    def test_lambda_function_arn_exists_with_defining_custom_name(self):
+        image, rie, image_name = self.tagged_name("customname")
 
-    @parameterized.expand([("x86_64", "8005"), ("arm64", "9005"), ("", "9055")])
-    def test_exception_returned(self, arch, port):
-        image, rie, image_name = self.tagged_name("exception", arch)
+        params = f"--name {image} --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context"
+        
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b'"My lambda ran succesfully"', r.content)
 
-        cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.exception_handler"
 
-        Popen(cmd.split(" ")).communicate()
+    def test_timeout_invoke(self):
+        image, rie, image_name = self.tagged_name("timeout")
 
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
+        params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=1 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.sleep_handler"
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(
-            b'{"errorMessage": "Raising an exception", "errorType": "Exception", "stackTrace": ["  File \\"/var/task/main.py\\", line 13, in exception_handler\\n    raise Exception(\\"Raising an exception\\")\\n"]}',
-            r.content,
-        )
-
-    @parameterized.expand([("x86_64", "8006"), ("arm64", "9006"), ("", "9056")])
-    def test_context_get_remaining_time_in_three_seconds(self, arch, port):
-        image, rie, image_name = self.tagged_name("remaining_time_in_three_seconds", arch)
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b"Task timed out after 1.00 seconds", r.content)
 
-        cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=3 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler"
 
-        Popen(cmd.split(' ')).communicate()
+    def test_exception_returned(self):
+        image, rie, image_name = self.tagged_name("exception")
 
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.exception_handler"
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-
-        # Execution time is not decided, 1.0s ~ 3.0s is a good estimation
-        self.assertLess(int(r.content), 3000)
-        self.assertGreater(int(r.content), 1000)
+        with self.create_container(params, image):
+            r = self.invoke_function()
+            
+            # Except the 3 fields assrted below, there's another field `request_id` included start from python3.12 runtime.
+            # We should ignore asserting the field `request_id` for it is in a UUID like format and changes everytime
+            result = r.json()
+            self.assertEqual(result["errorMessage"], "Raising an exception")
+            self.assertEqual(result["errorType"], "Exception")
+            self.assertEqual(result["stackTrace"], ["  File \"/var/task/main.py\", line 13, in exception_handler\n    raise Exception(\"Raising an exception\")\n"])
 
-    @parameterized.expand([("x86_64", "8007"), ("arm64", "9007"), ("", "9057")])
-    def test_context_get_remaining_time_in_ten_seconds(self, arch, port):
-        image, rie, image_name = self.tagged_name("remaining_time_in_ten_seconds", arch)
 
-        cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=10 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler"
+    def test_context_get_remaining_time_in_three_seconds(self):
+        image, rie, image_name = self.tagged_name("remaining_time_in_three_seconds")
 
-        Popen(cmd.split(' ')).communicate()
+        params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=3 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler"
 
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            # Execution time is not decided, 1.0s ~ 3.0s is a good estimation
+            self.assertLess(int(r.content), 3000)
+            self.assertGreater(int(r.content), 1000)
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
 
-        # Execution time is not decided, 8.0s ~ 10.0s is a good estimation
-        self.assertLess(int(r.content), 10000)
-        self.assertGreater(int(r.content), 8000)
+    def test_context_get_remaining_time_in_ten_seconds(self):
+        image, rie, image_name = self.tagged_name("remaining_time_in_ten_seconds")
 
-    @parameterized.expand([("x86_64", "8008"), ("arm64", "9008"), ("", "9058")])
-    def test_context_get_remaining_time_in_default_deadline(self, arch, port):
-        image, rie, image_name = self.tagged_name("remaining_time_in_default_deadline", arch)
+        params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=10 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler"
 
-        cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler"
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            # Execution time is not decided, 8.0s ~ 10.0s is a good estimation
+            self.assertLess(int(r.content), 10000)
+            self.assertGreater(int(r.content), 8000)
 
-        Popen(cmd.split(' ')).communicate()
 
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
+    def test_context_get_remaining_time_in_default_deadline(self):
+        image, rie, image_name = self.tagged_name("remaining_time_in_default_deadline")
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler"
 
-        # Executation time is not decided, 298.0s ~ 300.0s is a good estimation
-        self.assertLess(int(r.content), 300000)
-        self.assertGreater(int(r.content), 298000)
+        with self.create_container(params, image):
+            r = self.invoke_function()
 
-    @parameterized.expand([("x86_64", "8009"), ("arm64", "9009"), ("", "9059")])
-    def test_invoke_with_pre_runtime_api_runtime(self, arch, port):
-        image, rie, image_name = self.tagged_name("pre-runtime-api", arch)
+            # Executation time is not decided, 298.0s ~ 300.0s is a good estimation
+            self.assertLess(int(r.content), 300000)
+            self.assertGreater(int(r.content), 298000)
 
-        cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler"
 
-        Popen(cmd.split(" ")).communicate()
+    def test_invoke_with_pre_runtime_api_runtime(self):
+        image, rie, image_name = self.tagged_name("pre-runtime-api")
 
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler"
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b'"My lambda ran succesfully"', r.content)
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b'"My lambda ran succesfully"', r.content)
 
-    @parameterized.expand([("x86_64", "8010"), ("arm64", "9010"), ("", "9060")])
-    def test_function_name_is_overriden(self, arch, port):
-        image, rie, image_name = self.tagged_name("assert-overwritten", arch)
 
-        cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_env_var_is_overwritten"
+    def test_function_name_is_overriden(self):
+        image, rie, image_name = self.tagged_name("assert-overwritten")
 
-        Popen(cmd.split(" ")).communicate()
+        params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_env_var_is_overwritten"
 
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b'"My lambda ran succesfully"', r.content)
 
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b'"My lambda ran succesfully"', r.content)
 
-    @parameterized.expand([("x86_64", "8011"), ("arm64", "9011"), ("", "9061")])
-    def test_port_override(self, arch, port):
-        image, rie, image_name = self.tagged_name("port_override", arch)
+    def test_port_override(self):
+        image, rie, image_name = self.tagged_name("port_override")
 
         # Use port 8081 inside the container instead of 8080
-        cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8081 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler --runtime-interface-emulator-address 0.0.0.0:8081"
-
-        Popen(cmd.split(" ")).communicate()
-
-        # sleep 1s to give enough time for the endpoint to be up to curl
-        time.sleep(SLEEP_TIME)
-
-        r = requests.post(
-            f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={}
-        )
-        self.assertEqual(b'"My lambda ran succesfully"', r.content)
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8081 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler --runtime-interface-emulator-address 0.0.0.0:8081"
+
+        with self.create_container(params, image):
+            r = self.invoke_function()
+        
+            self.assertEqual(b'"My lambda ran succesfully"', r.content)
+
+
+    def test_custom_client_context(self):
+        image, rie, image_name = self.tagged_name("custom_client_context")
+
+        params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.custom_client_context_handler"
+
+        with self.create_container(params, image):
+            r = self.invoke_function(headers={
+                "X-Amz-Client-Context": base64.b64encode(json.dumps({
+                    "custom": {
+                        "foo": "bar",
+                        "baz": 123,
+                    }
+                }).encode('utf8')).decode('utf8'),
+            })
+            content = json.loads(r.content)
+            self.assertEqual("bar", content["foo"])
+            self.assertEqual(123, content["baz"])
 
 
 if __name__ == "__main__":
diff --git a/test/integration/testdata/Dockerfile-allinone b/test/integration/testdata/Dockerfile-allinone
index b804e5c..1d28406 100644
--- a/test/integration/testdata/Dockerfile-allinone
+++ b/test/integration/testdata/Dockerfile-allinone
@@ -1,4 +1,5 @@
-FROM public.ecr.aws/lambda/python:3.8
+ARG IMAGE_ARCH
+FROM public.ecr.aws/lambda/python:3.12-$IMAGE_ARCH
 
 WORKDIR /var/task
 COPY ./ ./
diff --git a/test/integration/testdata/main.py b/test/integration/testdata/main.py
index b6b527d..9757be8 100644
--- a/test/integration/testdata/main.py
+++ b/test/integration/testdata/main.py
@@ -41,3 +41,7 @@ def check_remaining_time_handler(event, context):
     # Wait 1s to see if the remaining time changes
     time.sleep(1)
     return context.get_remaining_time_in_millis()
+
+
+def custom_client_context_handler(event, context):
+    return context.client_context.custom