Template for a Python 3.14 app running on AWS ECS (Fargate). The app is packaged as a Docker image (ECR) and can run in one of these modes, selected by trigger_type:
- ecs_eventbridge – Scheduled task: EventBridge cron runs the ECS task (with optional SQS DLQ).
- ecs_api_service (legacy alias
ecs_service) – Always-on service: ECS Service behind a public Application Load Balancer (optional HTTPS + Route53 whenAPI_DOMAIN/API_ROOT_DOMAINare set). - ecs_internal_api_service – Internal API: same as above, but the ALB is internal, placed in private subnets, and only reachable from inside the VPC (service-to-service calls from ECS, EC2, or VPC-attached Lambda).
- ecs_background_service – Always-on worker: ECS Service with no ALB.
- Python 3.14, Poetry
- Docker
- Terraform (bootstrap + main; state in S3)
- GitHub Actions (test, deploy staging/prod, destroy via tags)
- Python 3.14 and Poetry
# macOS
brew install python@3.14 poetry
poetry config virtualenvs.in-project truepoetry env use python3.14
poetry installCreate .env in the project root with at least:
PYTHONPATH=app
Default (basic task):
poetry run python app/main.pyIf using the FastAPI option (uncomment in app/main.py and Dockerfile):
poetry run uvicorn app.main:app --host 0.0.0.0 --port 8080 --reloadpoetry run pytest tests/unit- AWS account with OIDC role and S3 bucket for Terraform state (e.g. NRD-Tech Terraform Bootstrap).
- VPC with separate public and private subnets (detected via
map-public-ip-on-launch; resources fall back to all VPC subnets when one side is empty, e.g. the default VPC). - Docker running (for local deploys; image is built and pushed by Terraform).
Run python3 setup.py (recommended), or edit config.global by hand. At minimum set:
APP_IDENT_WITHOUT_ENV– Short app name (e.g.my-app).TERRAFORM_STATE_BUCKET– S3 bucket for Terraform state.AWS_DEFAULT_REGION– e.g.us-west-2.AWS_ROLE_ARN– OIDC role ARN for the pipeline.LAUNCH_TYPE–FARGATEorFARGATE_SPOT.trigger_type–ecs_eventbridge(scheduled),ecs_api_service(public ALB + service),ecs_internal_api_service(internal ALB + service, VPC-only), orecs_background_service(service, no ALB).APP_CPU/APP_MEMORY– Task size (e.g.256/512).CPU_ARCHITECTURE–X86_64orARM64.- Optional:
VPC_NAME– tag:Name of a custom VPC; leave unset for the default VPC.
In config.staging and config.prod set MIN_COUNT / MAX_COUNT (service task counts for auto-scaling) and API_DOMAIN / API_ROOT_DOMAIN when using a custom domain with an API trigger.
Trigger type is controlled only by trigger_type; no need to comment or uncomment Terraform files.
- Staging:
ENVIRONMENT=staging ./deploy.sh - Production:
ENVIRONMENT=prod ./deploy.sh - Destroy:
ENVIRONMENT=staging ./deploy.sh -dorENVIRONMENT=prod ./deploy.sh -d
- Staging: Push to
main→ workflow runs tests then deploys staging. - Production: Push a tag
v*(e.g.git tag v1.0.0 && git push origin v1.0.0) → deploys production. - Destroy staging: Push tag
destroy-staging-*(e.g.destroy-staging-1). - Destroy production: Push tag
destroy-prod-*(e.g.destroy-prod-1).
In .github/workflows/github_flow.yml the workflow loads role-to-assume and aws-region from config.global (via the "Load configuration" step). Ensure those are set in config.global; no hardcoded role in the workflow file.
Build and run the same image that ECS uses (replace with your ECR URL and region):
aws ecr get-login-password --region us-west-2 | \
docker login --username AWS --password-stdin <account>.dkr.ecr.us-west-2.amazonaws.com
docker run --rm -p 8080:8080 <account>.dkr.ecr.us-west-2.amazonaws.com/<app_ident>_repository:latestFor the default task image: container runs and exits. For FastAPI: curl http://localhost:8080/ping.
docker inspect <account>.dkr.ecr.<region>.amazonaws.com/<app_ident>_repository:latest| Action | Command |
|---|---|
| Install dependencies | poetry install |
| Run script | poetry run python app/main.py |
| Run tests | poetry run pytest tests/unit |
| Add dependency | poetry add <package> |
| Add dev dependency | poetry add --group dev <package> |
| Export requirements | poetry export -f requirements.txt --output requirements.txt |
This project is under the MIT License. You may use it as a base for proprietary work; replace the LICENSE file and optionally add a NOTICE file as needed.
One-time: add the CodeArtifact source to pyproject.toml (see Poetry docs).
Daily: obtain token and configure auth:
export CODEARTIFACT_TOKEN=$(aws codeartifact get-authorization-token --domain <domain> --domain-owner <account> --query authorizationToken --output text)
poetry config http-basic.<domain> aws $CODEARTIFACT_TOKENUncomment the CodeArtifact block in config.global and the Dockerfile if the image build needs private packages.
See architecture.md in the repo root for diagrams and a short architecture description.