Skip to content

Create deployment docker setup (AWS series #671–675) #219#681

Open
drakeredwind01 wants to merge 8 commits into
mainfrom
drake_670_675_aws_deployment_series
Open

Create deployment docker setup (AWS series #671–675) #219#681
drakeredwind01 wants to merge 8 commits into
mainfrom
drake_670_675_aws_deployment_series

Conversation

@drakeredwind01

@drakeredwind01 drakeredwind01 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Fixes #219

AWS Deployment Series — Issues #671–675

A complete AWS production deployment setup built across five issues on branch drake_670_integrate_whitenoise.

Summary

Issue Title Key outcome
#671 Split requirements into dev and AWS Created requirements-aws.in/txt; corrected psycopg2-binary and added whitenoise
#672 Add gunicorn and Whitenoise Created settings_aws.py, urls_aws.py, updated Dockerfile-aws and entrypoint-aws.sh
#673 Optimize Dockerfile-aws with multi-stage build 18% image size reduction: 74MB → 60.7MB
#674 Create docker-compose-aws.yml for local testing Full local production test stack with docs
#675 Update deployment workflow Added DOCKER_BUILDKIT=1; updated aws-resources.md

Key discoveries across the series

  • psycopg2-binary was incorrectly classified as dev-only in the issue — it's the database driver, required everywhere
  • DJANGO_ALLOWED_HOSTS had no default, crashing collectstatic at Docker build time
  • drf_spectacular turned out to be a runtime import dependency (used in views.py decorators), not dev-only — had to be added back to requirements-aws.in
  • requirements-aws.txt compiled locally resolved packages incompatible with Python 3.10 in the Docker image — always compile with --python-version 3.10
  • Named postgres volumes must differ between dev and aws compose stacks to avoid shared database state

Split requirements into dev and AWS #671

Response for HFLA re: requirements classification

psycopg2-binary is marked as a dev dependency in the issue, but it's the PostgreSQL database driver — the app cannot connect to the database without it in any environment. It should be included in requirements-aws.in.

whitenoise was not listed in the issue but has already been added to requirements.in on the current branch; it belongs in requirements-aws.in as well since it serves static files in production.

Files created:

  • app/requirements-aws.in — 9 production-only packages (excludes django-linear-migrations, drf-spectacular, markdown, and all pytest packages)
  • app/requirements-aws.txt — compiled from the above; 16 resolved packages

Files updated:

  • docs/architecture/project_structure.md — added requirements-aws.in (10) and requirements-aws.txt (11) to the tree, renumbered setup.cfg to (12), and updated all caption descriptions to clarify dev vs. production purpose
  • scripts/update-dependencies.sh — added a second uv pip compile command for the AWS requirements file so both stay in sync when dependencies are upgraded

Add gunicorn and Whitenoise, and set up Dockerfile-aws and entrypoint-aws.sh #672

Pre-implementation concerns

1. INSTALLED_APPS references dev-only packages not in requirements-aws.txt (serious)

settings.py includes drf_spectacular and django_linear_migrations in INSTALLED_APPS. Neither is installed in the production image, so the container will crash with ModuleNotFoundError on startup.
Decision: create app/peopledepot/settings_aws.py that imports from settings.py and overrides INSTALLED_APPS to exclude dev-only apps. This is the industry standard approach (used by Cookiecutter Django). Set DJANGO_SETTINGS_MODULE=peopledepot.settings_aws in Dockerfile-aws.
Resolved: app/peopledepot/settings_aws.py created.
Note: uses from .settings import * — the accepted Django idiom for settings inheritance (settings are a config namespace, not a module; explicit imports would require listing every setting). # noqa: F401, F403 silences flake8, which doesn't know this is a settings file.

2. collectstatic during docker build crashes on missing DJANGO_ALLOWED_HOSTS

settings.py:37: os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") raises AttributeError when the env var is unset at build time, preventing Django settings from loading at all.
Decision: add a defaultos.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",") — so the build can proceed without that env var.
Resolved: settings.py:37 updated with default.

3. drf_spectacular is a runtime import dependency — missed in #672, fixed in #674

views.py imports drf_spectacular at the module level for @extend_schema decorators. Removing it from INSTALLED_APPS in settings_aws.py wasn't enough — it also needs to be installed. It was incorrectly classified as dev-only; it's actually a runtime import dependency of the application code.
Fix (applied in #674): add drf-spectacular back to requirements-aws.in; keep it out of INSTALLED_APPS so schema endpoints and management commands stay inactive in production. Also created urls_aws.py to exclude the schema UI routes.

4. Dockerfile-aws has graphviz and Roboto font

These were copied from the dev Dockerfile and serve no production purpose. Leaving them in — out of scope for this issue, can be a separate issue if HFLA wants them removed.

Files created:

  • app/peopledepot/settings_aws.py — inherits base settings, overrides INSTALLED_APPS (removes drf_spectacular and django_linear_migrations) and REST_FRAMEWORK (removes DEFAULT_SCHEMA_CLASS)

Files updated:

  • app/requirements-aws.in — added gunicorn
  • app/requirements-aws.txt — recompiled; now 18 packages including gunicorn 26.0.0
  • app/peopledepot/settings.py — added "localhost" default to DJANGO_ALLOWED_HOSTS so collectstatic doesn't crash at build time
  • app/Dockerfile-aws — uses requirements-aws.txt, sets DJANGO_SETTINGS_MODULE=peopledepot.settings_aws, runs collectstatic during build, adds BuildKit cache mounts, removes dev CMD
  • app/entrypoint-aws.sh — runs migrate --noinput then starts gunicorn on 0.0.0.0:8000

Optimize Dockerfile-aws with multi-stage build #673

Pre-implementation concerns

1. Must build and measure the single-stage image before refactoring

The acceptance criteria requires a before/after size comparison. Need to build and record the current image size first, then implement the multi-stage build, then measure again. Do not start the refactor until the baseline is captured.

2. Size savings will be modest — I agree

The cache mounts (--mount=type=cache) added in #672 already keep pip/apk caches out of image layers, which was historically the biggest multi-stage win. Savings here will mainly be removing pip and uv from the final image. The issue itself flags this uncertainty; I confirmed the expectation going in.

3. staticfiles/ must be explicitly copied from builder to runtime stage (foresight)

collectstatic runs in the builder stage and writes to staticfiles/. This directory must be explicitly COPY --from=builder'd into the runtime stage — whitenoise serves from it at runtime. Pre-emptively noted to avoid missing it during implementation.

size before

docker build -f app/Dockerfile-aws -t peopledepot-aws:single-stage app/
docker image inspect peopledepot-aws:single-stage --format='{{.Size}}' | numfmt --to=inspect
71M
docker images peopledepot-aws:single-stage
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
peopledepot-aws:single-stage fded4b4e4602 330MB 74MB

reclaim space

docker rmi peopledepot-aws:single-stage

size after

docker build -f app/Dockerfile-aws -t peopledepot-aws:multi-stage app/
docker images peopledepot-aws:multi-stage
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
peopledepot-aws:multi-stage 56eed5b30e55 282MB 60.7MB

reclaim space

docker rmi peopledepot-aws:multi-stage

size diff

18% reduction (74MB → 60.7MB)

Files updated:

  • app/Dockerfile-aws — refactored to multi-stage; builder stage installs deps and runs collectstatic, runtime stage copies site-packages, gunicorn binary, and app code from builder; graphviz and font in runtime stage only

Create docker-compose-aws.yml for local testing #674

Pre-implementation concerns (not mentioned in the issue)

1. Must NOT mount the app directory as a volume

docker-compose.yml (dev) mounts ./app/:/usr/src/app/ so developers can edit code and see changes instantly without rebuilding the image — this is intentional for development workflow.
docker-compose-aws.yml must NOT do this. The app code is baked into the image during the build (COPY . . in Dockerfile-aws). Mounting the directory would overwrite it with live local code, bypassing collectstatic, the multi-stage build, and settings_aws.py — nothing production-like would actually be tested.
Proposed change: omit the volume mount entirely from docker-compose-aws.yml.

2. Need a new .env.docker-aws file

.env.docker has DEBUG=True — wrong for production testing. .env.prod.sample has SECURE_SSL_REDIRECT=True — causes an infinite redirect loop locally since there's no SSL certificate. Neither works as-is.
Proposed change: create app/.env.docker-aws and app/.env.docker-aws-example based on .env.prod.sample but with DEBUG=False, SSL settings off, and local-friendly DJANGO_ALLOWED_HOSTS.

3. Port conflict with dev compose

Dev compose binds host ports 8000 (web) and 5432 (postgres). Running both simultaneously would fail.
Proposed change: use 8001:8000 and 5433:5432 in docker-compose-aws.yml. Dev keeps the familiar localhost:8000; production is accessible at localhost:8001. The compose file handles the mapping — developers don't need to remember the port numbers.
Port notation is host:container. The left side (host port) is where the conflict would happen — dev uses 5432, aws uses 5433, so both can run simultaneously. The right side (5432) is PostgreSQL's internal port inside each container — they're separate containers so that never conflicts.

4. Postgres named volume must differ from dev

Dev compose uses named volume postgres_data. If docker-compose-aws.yml used the same name, both environments would share the same database on disk — migration conflicts and data bleed between environments.
Resolved: use postgres_data_aws in docker-compose-aws.yml.

5. .env.docker-aws not covered by .gitignore

The gitignore lists explicit filenames rather than a wildcard, so .env.docker-aws would have been committed with secrets.
Resolved: .env.docker-aws added to .gitignore.

Files created:

  • docker-compose-aws.yml — builds Dockerfile-aws, ports 8001:8000 and 5433:5432, postgres_data_aws volume, no code volume mount
  • app/.env.docker-aws-example — production-like env: DEBUG=False, SSL settings off, local ALLOWED_HOSTS; committed as template
  • app/.env.docker-aws — gitignored developer copy of the above
  • docs/contributing/howto/test-aws-setup-locally.md — full guide: setup steps, cleanup commands, comparison table of dev vs aws environments
  • docs/contributing/onboarding/test_aws_locally.md — two commands and a link to the full guide

Files updated:

  • .gitignore — added .env.docker-aws
  • docs/contributing/howto/index.md — added link to new guide

Issues found during testing and fixes applied

drf_spectacular still imported via views.py

views.py imports drf_spectacular at the module level for @extend_schema decorators. Removing it from INSTALLED_APPS wasn't enough — include("core.api.urls") triggers views.py import at startup.
Fix: added drf-spectacular back to requirements-aws.in (it's a runtime import dependency, not dev-only); created app/peopledepot/urls_aws.py without schema UI routes; added ROOT_URLCONF = "peopledepot.urls_aws" to settings_aws.py.

requirements-aws.txt compiled against wrong Python version

Recompiling locally resolved rpds-py==2026.5.1 which requires Python>=3.11, but Dockerfile-aws uses Python 3.10 — build failed.
Fix: recompiled with uv pip compile --python-version 3.10; updated scripts/update-dependencies.sh to always pass --python-version 3.10 for the aws compile command.

Files created (including fixes):

  • docker-compose-aws.yml
  • app/.env.docker-aws-example
  • app/.env.docker-aws
  • app/peopledepot/urls_aws.py — production URL config without schema UI routes
  • docs/contributing/howto/test-aws-setup-locally.md
  • docs/contributing/onboarding/test_aws_locally.md

Files updated (including fixes):

  • .gitignore — added .env.docker-aws
  • app/requirements-aws.in — added drf-spectacular
  • app/requirements-aws.txt — recompiled targeting Python 3.10
  • app/peopledepot/settings_aws.py — added ROOT_URLCONF = "peopledepot.urls_aws"
  • scripts/update-dependencies.sh — added --python-version 3.10 to aws compile command
  • docs/contributing/howto/index.md — added link to new guide

Update deployment workflow #675

Most action items already handled pre-emptively in #671–674

  • deploy-dev.yml already uses docker build -f Dockerfile-aws
  • requirements-aws.txt is referenced inside Dockerfile-aws — picked up automatically
  • settings_aws.py is set via ENV DJANGO_SETTINGS_MODULE in Dockerfile-aws — baked in
  • entrypoint-aws.sh is the ENTRYPOINT in Dockerfile-aws — already correct
  • ✓ gunicorn is started by entrypoint-aws.sh — already in place
  • ✓ Workflow pushes to ECR and triggers ECS redeployment — unchanged and working

Pre-implementation concerns

1. BuildKit not explicitly enabled — NOT mentioned in the issue

Dockerfile-aws uses --mount=type=cache which requires BuildKit. GitHub Actions enables BuildKit by default on recent Docker versions but it is not guaranteed and is not explicitly set in the workflow. If missing, the build fails with a syntax error on the --mount lines.
Proposed change: add DOCKER_BUILDKIT=1 to the build step's env block in deploy-dev.yml.

2. Documentation update — IS mentioned in the issue

docs/contributing/reference/aws-resources.md already references Dockerfile-aws and entrypoint-aws.sh and is mostly current. Needs minor additions to mention gunicorn as the WSGI server, whitenoise for static files, and settings_aws.py as the production settings module.

Files updated:

  • .github/workflows/deploy-dev.yml — added DOCKER_BUILDKIT=1 to build step env block
  • docs/contributing/reference/aws-resources.md — step 5 updated to mention gunicorn and whitenoise; settings_aws.py added to Docker & App Startup section

@@ -0,0 +1,27 @@
from .settings import * # noqa: F401, F403
@drakeredwind01 drakeredwind01 changed the title Drake 670 675 aws deployment series Create deployment docker setup (AWS series #671–675) #219 Jun 11, 2026

@fyliu fyliu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. But can you create separate PRs for each issue so that it's easier to review? These points may belong to different issues, but I can't be sure. There might be more issues, but these are the more visible ones.

  • Please remove the first commit from the commit chain. That commit is attempting to implement a "not planned" issue.
  • Please fix the code quality comment about importing all.
  • Please test that the changes work before creating the PR. The MkDocs service is crashing.

@github-project-automation github-project-automation Bot moved this to PR changes requested in P: PD: Project Board Jun 11, 2026
@drakeredwind01
drakeredwind01 requested a review from fyliu June 11, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: PR changes requested

Development

Successfully merging this pull request may close these issues.

Create deployment docker setup (for dev env)

2 participants