What runs where
| AWS service | Local stand-in | Your local workflow |
|---|---|---|
| ECS task, Fargate task | Docker Compose with the task’s images | Run your services together with healthchecks and published ports |
| Lambda (container image) | Lambda base image with the Runtime Interface Emulator | Invoke your function over HTTP on native arm64 |
| S3 and other AWS APIs | LocalStack | Develop S3 bucket and object operations locally |
| EKS | kubectl and k9s against the real cluster; kwok for a local API | Build and test locally, then use kubectl and k9s with your cluster |
ECS and Fargate tasks as a Compose project
An ECS task definition is a list of containers with images, environment, port mappings, health checks and start order. Compose describes the same things, so the translation is direct: one service per container definition.
| Task definition | compose.yaml |
|---|---|
image | image |
environment, secrets | environment, env_file (keep real secrets out of the file) |
portMappings | ports |
healthCheck | healthcheck |
dependsOn with HEALTHY | depends_on with condition: service_healthy |
runtimePlatform.cpuArchitecture | platform: linux/amd64 or linux/arm64 |
services:
api:
image: ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/api:1.4.0
platform: linux/amd64 # match the task's cpuArchitecture
environment:
DATABASE_URL: postgres://postgres:dev@db:5432/app
ports: ["8080:8080"]
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD: dev
POSTGRES_DB: app
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 2s
retries: 30 docker compose up -d --wait # returns when db is healthy and api has started
curl localhost:8080/health
docker compose down -v We verified this pattern on Oxcart with Postgres 17 and an nginx service gated on service_healthy: up --wait returned with both healthy and the published port answering on localhost. For amd64 images built on your Mac, pass the same platform when you run them; a locally built amd64-only image is found only when --platform linux/amd64 (or platform: in Compose) is set.
Run and invoke a Lambda container image locally
- Write the handler. Create
app.pywith a functionhandler(event, context)that returns a dict, for example{"statusCode": 200, "body": "hello"}. - Write the Dockerfile. Start from an AWS Lambda base image, which includes the Runtime Interface Emulator:
FROM public.ecr.aws/lambda/python:3.13, thenCOPY app.py ${LAMBDA_TASK_ROOT}/andCMD ["app.handler"]. - Build the image. Run
docker build -t my-lambda .. Add--platform linux/amd64if your function is configured for x86_64. - Run it. Run
docker run -d -p 9000:8080 my-lambda. The emulator listens on port 8080 inside the container. - Invoke it. Run
curl -XPOST localhost:9000/2015-03-31/functions/function/invocations -d '{}'. The response is your handler’s return value, anddocker logsshows the Lambda REPORT line with the duration.
In our run the function answered {"statusCode": 200, "body": "hello from lambda on aarch64"}, and the container log showed the Lambda REPORT line with a 19 ms duration. The emulator is AWS’s own: “a proxy for Lambda’s Runtime and Extensions APIs” that stands up the /2015-03-31/functions/function/invocations endpoint.
AWS APIs with LocalStack
LocalStack emulates AWS APIs in one container on port 4566. On Oxcart we ran the 4.9 image and exercised S3 end to end:
docker run -d --name localstack -p 4566:4566 localstack/localstack:4.9
curl -s localhost:4566/_localstack/health # s3, lambda, dynamodb… "available"
curl -X PUT localhost:4566/my-bucket # create a bucket
curl -X PUT --data-binary hello localhost:4566/my-bucket/hello.txt
curl localhost:4566/my-bucket/hello.txt # hello Know the licensing before you build on it. Since March 23, 2026, the latest LocalStack releases require a LocalStack account and auth token; without LOCALSTACK_AUTH_TOKEN, the latest image exits with “License activation failed”. LocalStack’s free Hobby plan is for non-commercial use, ECS is available from its Base plan and EKS from its Ultimate plan.
Push to ECR and deploy
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.us-east-1.amazonaws.com
docker buildx build --platform linux/amd64 --load -t ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/api:1.4.0 .
docker push ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/api:1.4.0 Oxcart keeps the registry password in the macOS Keychain, not in ~/.docker/config.json. Pushing a multi-platform image from Oxcart is not supported: push one tag per architecture, or publish multi-arch manifests from CI.
EKS from your Mac
aws eks update-kubeconfig --name my-cluster adds the cluster to your kubeconfig so you can use kubectl and k9s from macOS. Develop and test your services locally with Oxcart, then deploy the same images to EKS. For local manifest and controller testing, kwok provides a real Kubernetes API server with simulated nodes. The Kubernetes on a Mac guide explains how these workflows fit together.
Why the local loop is worth it
Every change you test locally skips a push, a deploy and a log search. Oxcart makes that loop short: 42 ms for docker run --rm, 0.69 s to create and start 20 containers, and 77 ms to stop and remove them, the fastest of the three runtimes we tested. Outbound connections from containers are opened by your Mac, so a VPN into your AWS network applies to them too.
Questions
How do I test an ECS or Fargate task locally on a Mac?
Run the task’s containers with Docker Compose: one service per container definition, with the same image, environment, port mappings, healthcheck and dependencies. docker compose up -d --wait returns when every service with a healthcheck is healthy. Oxcart runs the stack with healthcheck dependencies and published ports, so you can exercise the services together before deployment.
Can I run AWS Lambda container images locally on Apple silicon?
Yes. AWS Lambda base images include the Runtime Interface Emulator. On Oxcart we built a Python 3.13 function from public.ecr.aws/lambda/python:3.13, ran it with -p 9000:8080, and invoked it with curl: it answered HTTP 200 natively on arm64.
Does LocalStack work on Oxcart?
Yes, as a container. We verified S3 bucket create, put, get and list against localhost:4566 with the LocalStack 4.9 image. Current LocalStack releases need a LocalStack account and auth token, set as LOCALSTACK_AUTH_TOKEN; without one, the latest image exits. LocalStack documents ECS from its Base plan and EKS from its Ultimate plan.
How do I push an image to Amazon ECR from a Mac?
Log in with aws ecr get-login-password --region REGION | docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.REGION.amazonaws.com, then tag and docker push. On Oxcart, docker login stores the password in the macOS Keychain.
Should I build amd64 or arm64 images for Fargate?
Match the task definition’s runtime platform. If it uses ARM64, build native arm64 images on your Mac; if it uses X86_64, build with --platform linux/amd64. Oxcart runs amd64 images locally through Rosetta, so you can test the exact image you deploy.
How do I develop for EKS on my Mac?
Build and test your app and its dependencies locally with Oxcart, then push the images to ECR. Use kubectl and k9s on the Mac to work with your EKS cluster, and kwok on Oxcart to check manifests against a local Kubernetes API. See the Kubernetes on a Mac guide.
Sources and method
- Examples tested with Oxcart 0.1.2, Mac17,14, macOS 27.2, 2026-10-10 (Compose, Lambda base image with the emulator, LocalStack 4.9 S3, Buildx).
- LocalStack auth: https://blog.localstack.cloud/localstack-single-image-next-steps/; plans: https://localstack.cloud/pricing/; ECS: https://docs.localstack.cloud/aws/services/ecs/; EKS: https://docs.localstack.cloud/aws/services/eks/.
- Lambda Runtime Interface Emulator: https://github.com/aws/aws-lambda-runtime-interface-emulator. AWS SAM CLI and Docker: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-docker.html. ECS Local Container Endpoints: https://github.com/awslabs/amazon-ecs-local-container-endpoints. Checked 2026-10-10.
- Container speed: Oxcart 0.1.2, 2026-10-10, Apple M5 Max, 8 CPUs and 12 GiB, medians of five samples; the other runtimes were measured earlier the same day on the same Mac and harness with the docker 29.4 client.