Context
The Notify.gov API currently relies on Cloud Foundry (cloud.gov) for deployment and requires terraform/development/run.sh to provision local AWS credentials for development. This setup has several limitations:
- Environment Parity Gap: Local development does not mirror the full production stack - developers run Flask and Celery directly via
make run-procfile without containerization
- Cloud Dependency: Developers need real AWS credentials provisioned via Terraform for S3, SES, and SNS even during local development
- Onboarding Friction: New team members must navigate complex setup steps (
make bootstrap, Terraform provisioning) before sending their first test notification
- Testing Gaps: Integration tests against AWS services are limited because they require real cloud credentials
- Migration Preparation: As part of the broader infrastructure migration from Cloud Foundry to AWS (App Runner/ECS), we need Docker-based local development as the foundation for containerized deployments
The AWS clients (app/clients/sms/aws_sns.py and app/clients/email/aws_ses.py) already use boto3 for AWS communication. The SNS client already supports LOCALSTACK_ENDPOINT_URL for local testing, but the SES client does not have this capability.
Decision
We will implement Docker-based local development using LocalStack to simulate AWS services. This involves:
1. Multi-stage Dockerfile
We will create a multi-stage Dockerfile with distinct build targets:
base: Common Python dependencies and application code
web: Flask/Gunicorn web server (port 6011)
worker: Celery worker with gevent pool
scheduler: Celery beat scheduler
2. Docker Compose Service Composition
A docker-compose.yml will orchestrate the full stack:
db: PostgreSQL 15 for notification data
redis: Redis 7.0 for Celery task queue and caching
localstack: LocalStack container with S3, SES, and SNS services
api: Flask web server (build target: web)
worker: Celery worker (build target: worker)
scheduler: Celery beat (build target: scheduler)
3. LocalStack Initialization
An initialization script (docker/localstack/init-aws.sh) will provision:
- S3 bucket for CSV uploads (
notify-csv-uploads)
- SNS topic for SMS delivery receipts
- SES verified email identity for local testing
4. boto3 endpoint_url Pattern
We will modify app/clients/email/aws_ses.py to support LocalStack by checking for LOCALSTACK_ENDPOINT_URL environment variable and passing it as endpoint_url to the boto3 client, matching the existing pattern in aws_sns.py.
5. Makefile Targets
New targets for Docker operations:
docker-up: Start full Docker stack
docker-down: Stop Docker stack
docker-build: Build Docker images
docker-logs: View Docker logs
docker-migrate: Run database migrations
Why Docker + LocalStack over current approach:
- Environment parity: Containers match production deployment model
- Zero cloud dependency: No AWS credentials needed for local development
- Fast onboarding: Single
make docker-up starts entire stack
- Comprehensive testing: Full AWS service simulation enables integration testing
- Migration path: Docker images become the deployment artifact for AWS App Runner/ECS
Alternatives not chosen:
- moto library only: Requires code changes for each test, doesn't provide running services
- Terraform localstack provider: Adds complexity without Docker orchestration benefits
- AWS SAM Local: Designed for Lambda, not suitable for long-running Flask/Celery services
Consequences
Positive:
- Developers can run the full notification stack with
make docker-up and no cloud credentials
- New team members can be productive within minutes of cloning the repository
- Integration tests can run against LocalStack services in CI/CD pipelines
- Docker images created for local development become the production deployment artifacts
- Environment consistency reduces "works on my machine" issues
- LocalStack provides realistic AWS service behavior including error conditions and throttling simulation
Negative:
- Docker and Docker Compose become development prerequisites (most developers already have these)
- LocalStack has some behavioral differences from real AWS services (e.g., no actual email delivery)
- Additional disk space required for Docker images (estimated ~2GB for full stack)
- Developers must learn Docker Compose commands alongside existing Makefile targets
Neutral:
- The existing
make bootstrap and native Python development workflow will remain available for developers who prefer it
- This change does not modify production deployment - it only affects local development
Author
@jim
Stakeholders
@ccostino @stvnrlly @kenkehl
Next Steps
Once accepted, implementation will proceed in these phases:
-
Create Docker infrastructure (Phase 1 of AWS migration plan)
-
Modify application code for LocalStack support
-
Documentation updates
Context
The Notify.gov API currently relies on Cloud Foundry (cloud.gov) for deployment and requires
terraform/development/run.shto provision local AWS credentials for development. This setup has several limitations:make run-procfilewithout containerizationmake bootstrap, Terraform provisioning) before sending their first test notificationThe AWS clients (
app/clients/sms/aws_sns.pyandapp/clients/email/aws_ses.py) already use boto3 for AWS communication. The SNS client already supportsLOCALSTACK_ENDPOINT_URLfor local testing, but the SES client does not have this capability.Decision
We will implement Docker-based local development using LocalStack to simulate AWS services. This involves:
1. Multi-stage Dockerfile
We will create a multi-stage Dockerfile with distinct build targets:
base: Common Python dependencies and application codeweb: Flask/Gunicorn web server (port 6011)worker: Celery worker with gevent poolscheduler: Celery beat scheduler2. Docker Compose Service Composition
A
docker-compose.ymlwill orchestrate the full stack:db: PostgreSQL 15 for notification dataredis: Redis 7.0 for Celery task queue and cachinglocalstack: LocalStack container with S3, SES, and SNS servicesapi: Flask web server (build target: web)worker: Celery worker (build target: worker)scheduler: Celery beat (build target: scheduler)3. LocalStack Initialization
An initialization script (
docker/localstack/init-aws.sh) will provision:notify-csv-uploads)4. boto3 endpoint_url Pattern
We will modify
app/clients/email/aws_ses.pyto support LocalStack by checking forLOCALSTACK_ENDPOINT_URLenvironment variable and passing it asendpoint_urlto the boto3 client, matching the existing pattern inaws_sns.py.5. Makefile Targets
New targets for Docker operations:
docker-up: Start full Docker stackdocker-down: Stop Docker stackdocker-build: Build Docker imagesdocker-logs: View Docker logsdocker-migrate: Run database migrationsWhy Docker + LocalStack over current approach:
make docker-upstarts entire stackAlternatives not chosen:
Consequences
Positive:
make docker-upand no cloud credentialsNegative:
Neutral:
make bootstrapand native Python development workflow will remain available for developers who prefer itAuthor
@jim
Stakeholders
@ccostino @stvnrlly @kenkehl
Next Steps
Once accepted, implementation will proceed in these phases:
Create Docker infrastructure (Phase 1 of AWS migration plan)
docker/Dockerfilewith multi-stage buildsdocker/docker-compose.ymlwith all servicesdocker/localstack/init-aws.shinitialization script.env.docker.exampletemplate fileMakefileModify application code for LocalStack support
app/clients/email/aws_ses.pyto supportLOCALSTACK_ENDPOINT_URLDocumentation updates