Mapping a Django or FastAPI Backend to AWS
coreadvancedA Django or FastAPI project already names every piece it needs: a WSGI or ASGI server, a database, a cache, a place for uploads, a task queue, somewhere for settings and secrets. Deploying to AWS is mapping each of those to a managed service and deciding what the process boundaries are. Nothing about the application changes shape — what changes is who runs each piece.
Think of it as
Read your settings file as a shopping list. `DATABASES` becomes RDS, `CACHES` becomes ElastiCache, `DEFAULT_FILE_STORAGE` becomes S3, `CELERY_BROKER_URL` becomes SQS, and everything you read from an environment variable becomes Secrets Manager or Parameter Store. If a line in settings has no service next to it, that line is still running on somebody's laptop.
What we're doing: Turn a working local project into a deployable task definition without changing application code.
- 1
- The two-column form is the useful artifact: anything with a blank right-hand column is unfinished work.
- 9
- Same image, different command. This is what makes the worker guaranteed to run the code that was tested.
- 16
- The container gets an endpoint and a secret ARN; it never gets a password in an environment variable.
- 22
- Removing the file log handler is a real code change and the only one on this list — everything else is configuration.
Why this works: Almost every deployment problem at this stage comes from a line in the local setup that has no AWS counterpart — a local directory for uploads, a `.env` committed to the repo, a cron entry on one machine. Writing the mapping out as two columns forces each of those into the open before the first deploy, rather than after the first incident.
Building a separate image for the worker
Wrong
Better
What you see: A task fails on a model field the web tier already deployed, because the worker image is one build behind and nobody noticed the version skew.
Why: Web and worker share models, migrations and serializers, so they must share a version. Two images means two pipelines that can drift, and the drift is invisible until a worker deserializes a payload the newer web tier produced. One image with two commands makes skew impossible to introduce by accident.
- Front door and edge
- Route 53 — api.example.com → ALB alias
- CloudFront — static assets and cached GETs
- ALB — TLS, health checks, path rules
- Your code — one image, two commands
- ECS web service — gunicorn / uvicorn, behind the ALB
- ECS worker service — celery worker, no load balancer
- Scheduled task — celery beat, or an EventBridge schedule
- State — none of it in your container
- RDS / Aurora PostgreSQL — DATABASES, Multi-AZ
- ElastiCache Redis — CACHES, sessions, locks
- S3 — media uploads and static files
- SQS — CELERY_BROKER_URL
- Cross-cutting
- Secrets Manager — DB password, API keys, SECRET_KEY
- IAM task role — the container's only credentials
- CloudWatch — logs from stdout, metrics, alarms
The mapping, line by line
Together
ALB or API Gateway in front of the same application
Together
Remember: Read the settings file as a list of services to rent: RDS for DATABASES, ElastiCache for CACHES, S3 for uploads and static, SQS for the Celery broker, Secrets Manager for anything sensitive, CloudWatch for stdout. One image runs both web and worker; the task role, not an access key, is the container's identity.
See also: deploying a web service end to end · secrets manager and parameter store · static media jobs and migrations · three tier web application

