diff --git a/app/.env.docker-aws-example b/app/.env.docker-aws-example new file mode 100644 index 00000000..387070ec --- /dev/null +++ b/app/.env.docker-aws-example @@ -0,0 +1,31 @@ +DEBUG=False +SECRET_KEY=replace-me-with-a-secret-key +DJANGO_PORT=8000 +DJANGO_ALLOWED_HOSTS="localhost,127.0.0.1" + +SECURE_HSTS_SECONDS=0 +SECURE_HSTS_INCLUDE_SUBDOMAINS=False +SECURE_HSTS_PRELOAD=False +SECURE_SSL_REDIRECT=False +SESSION_COOKIE_SECURE=False +CSRF_COOKIE_SECURE=False + +# settings for db container environment variables in compose file +POSTGRES_USER=people_depot +POSTGRES_PASSWORD=people_depot +POSTGRES_DB=people_depot_aws + +# postgres settings for docker +SQL_USER=people_depot +SQL_PASSWORD=people_depot +SQL_DATABASE=people_depot_aws +SQL_ENGINE=django.db.backends.postgresql +SQL_HOST=db +SQL_PORT=5432 +DATABASE=postgres + +COGNITO_DOMAIN=peopledepot +COGNITO_AWS_REGION=us-west-2 +COGNITO_USER_POOL=us-west-2_Fn4rkZpuB + +PEOPLE_DEPOT_API_SECRET=people-depot-api-secret diff --git a/app/Dockerfile-aws b/app/Dockerfile-aws index af4607d4..8912ebd4 100644 --- a/app/Dockerfile-aws +++ b/app/Dockerfile-aws @@ -7,9 +7,13 @@ WORKDIR /usr/src/app # set environment variables ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 +ENV PYTHONPYCACHEPREFIX=/root/.cache/pycache/ +ENV DJANGO_SETTINGS_MODULE=peopledepot.settings_aws # install system dependencies RUN \ + --mount=type=cache,target=/var/cache/apk \ + --mount=type=cache,target=/etc/apk/cache \ apk add \ 'graphviz=~12' @@ -18,27 +22,25 @@ COPY Roboto-Regular.ttf /root/.fonts/ RUN fc-cache -f # install dependencies -COPY ./requirements.txt . +COPY ./requirements-aws.txt . # hadolint ignore=DL3042 RUN \ + --mount=type=cache,target=/root/.cache \ pip install uv==0.1.15 \ - && uv pip install --system -r requirements.txt - + && uv pip install --system -r requirements-aws.txt # copy project COPY . . -# copy entrypoint-aws.sh +# collect static files +RUN python manage.py collectstatic --noinput + +# copy and configure entrypoint COPY ./entrypoint-aws.sh . RUN sed -i 's/\r$//g' /usr/src/app/entrypoint-aws.sh \ && chmod +x /usr/src/app/entrypoint-aws.sh -# Expose the Django port +# Expose the gunicorn port EXPOSE 8000 - -# run entrypoint.sh ENTRYPOINT ["/usr/src/app/entrypoint-aws.sh"] - -# Run Django’s development server -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/app/entrypoint-aws.sh b/app/entrypoint-aws.sh index 9db9e617..df9d1e17 100755 --- a/app/entrypoint-aws.sh +++ b/app/entrypoint-aws.sh @@ -1,5 +1,5 @@ #!/bin/sh -python manage.py migrate +python manage.py migrate --noinput -exec "$@" +exec gunicorn peopledepot.wsgi:application --bind 0.0.0.0:8000 diff --git a/app/peopledepot/settings.py b/app/peopledepot/settings.py index 27fb1879..d41a2f86 100644 --- a/app/peopledepot/settings.py +++ b/app/peopledepot/settings.py @@ -34,7 +34,7 @@ # 'DJANGO_ALLOWED_HOSTS' should be a single string of hosts with a space between each. # For example: 'DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]' -ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",") SECURE_HSTS_SECONDS = int(os.getenv("SECURE_HSTS_SECONDS", "0")) diff --git a/app/peopledepot/settings_aws.py b/app/peopledepot/settings_aws.py new file mode 100644 index 00000000..85852ace --- /dev/null +++ b/app/peopledepot/settings_aws.py @@ -0,0 +1,37 @@ +from .settings import * # noqa: F401, F403 + +ROOT_URLCONF = "peopledepot.urls_aws" + +STATIC_ROOT = BASE_DIR / "staticfiles" +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + # 3rd party + "django_extensions", + "rest_framework", + "phonenumber_field", + "timezone_field", + # Local + "core", + "data", +] + +REST_FRAMEWORK = { + "DEFAULT_PERMISSION_CLASSES": ("core.api.permissions.DenyAny",), + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_jwt.authentication.JSONWebTokenAuthentication", + ), +} diff --git a/app/peopledepot/urls_aws.py b/app/peopledepot/urls_aws.py new file mode 100644 index 00000000..e63d49d2 --- /dev/null +++ b/app/peopledepot/urls_aws.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from django.http import HttpResponse +from django.urls import include +from django.urls import path + + +def index(request): + return HttpResponse("You're at the peopledepot index.") + + +urlpatterns = [ + path("", index), + path("admin/", admin.site.urls), + path("api/v1/", include("core.api.urls")), +] diff --git a/app/requirements-aws.in b/app/requirements-aws.in new file mode 100644 index 00000000..efc5ac33 --- /dev/null +++ b/app/requirements-aws.in @@ -0,0 +1,11 @@ +django~=4.2.27 +django-extensions +django-phonenumber-field[phonenumbers] +django-timezone-field +djangorestframework +drf-jwt +drf-spectacular +gunicorn +psycopg2-binary +tzdata +whitenoise diff --git a/app/requirements-aws.txt b/app/requirements-aws.txt new file mode 100644 index 00000000..62bfad3a --- /dev/null +++ b/app/requirements-aws.txt @@ -0,0 +1,76 @@ +asgiref==3.11.1 + # via django +attrs==26.1.0 + # via + # jsonschema + # referencing +cffi==2.0.0 + # via cryptography +cryptography==48.0.1 + # via pyjwt +django==4.2.30 + # via + # -r requirements-aws.in + # django-extensions + # django-phonenumber-field + # django-timezone-field + # djangorestframework + # drf-jwt + # drf-spectacular +django-extensions==4.1 + # via -r requirements-aws.in +django-phonenumber-field==8.4.0 + # via -r requirements-aws.in +django-timezone-field==7.2.2 + # via -r requirements-aws.in +djangorestframework==3.17.1 + # via + # -r requirements-aws.in + # drf-jwt + # drf-spectacular +drf-jwt==1.19.2 + # via -r requirements-aws.in +drf-spectacular==0.29.0 + # via -r requirements-aws.in +gunicorn==26.0.0 + # via -r requirements-aws.in +inflection==0.5.1 + # via drf-spectacular +jsonschema==4.26.0 + # via drf-spectacular +jsonschema-specifications==2025.9.1 + # via jsonschema +packaging==26.2 + # via gunicorn +phonenumbers==9.0.32 + # via django-phonenumber-field +psycopg2-binary==2.9.12 + # via -r requirements-aws.in +pycparser==3.0 + # via cffi +pyjwt==2.13.0 + # via drf-jwt +pyyaml==6.0.3 + # via drf-spectacular +referencing==0.37.0 + # via + # jsonschema + # jsonschema-specifications +rpds-py==0.30.0 + # via + # jsonschema + # referencing +sqlparse==0.5.5 + # via django +typing-extensions==4.15.0 + # via + # asgiref + # cryptography + # pyjwt + # referencing +tzdata==2026.2 + # via -r requirements-aws.in +uritemplate==4.2.0 + # via drf-spectacular +whitenoise==6.12.0 + # via -r requirements-aws.in diff --git a/docker-compose-aws.yml b/docker-compose-aws.yml new file mode 100644 index 00000000..7232fc99 --- /dev/null +++ b/docker-compose-aws.yml @@ -0,0 +1,23 @@ +services: + web: + build: + context: ./app + dockerfile: Dockerfile-aws + platform: linux/amd64 + ports: + - 8001:8000 + env_file: + - ./app/.env.docker-aws + depends_on: + - db + db: + image: postgres:13.0-alpine + volumes: + - postgres_data_aws:/var/lib/postgresql/data/ + ports: + - 5433:5432 + env_file: + - ./app/.env.docker-aws + +volumes: + postgres_data_aws: diff --git a/docs/architecture/project_structure.md b/docs/architecture/project_structure.md index 39e82098..5a213b95 100644 --- a/docs/architecture/project_structure.md +++ b/docs/architecture/project_structure.md @@ -37,7 +37,9 @@ app/ ├── manage.py # (7)! ├── requirements.in # (8)! ├── requirements.txt # (9)! -└── setup.cfg # (10)! +├── requirements-aws.in # (10)! +├── requirements-aws.txt # (11)! +└── setup.cfg # (12)! ``` 1. The core app in django. This app contains the API and models. See [Core App](#core-app) below for details. @@ -47,8 +49,10 @@ app/ 1. Dockerfile used to build the Docker image. 1. Entrypoint script called by the Docker image. 1. Django manage.py script. In nearly all cases, there's no good reason to change this. Just leave it alone. -1. Requirements.in file used by `uv pip compile`. See the [uv tool](uv.md) for details. -1. Requirements.txt file generated by `uv pip install`. Do not modify this file. Edit the `requirements.in` file instead. See the [uv tool](uv.md) for details. +1. Development requirements source file used by `uv pip compile`. Contains all dependencies including dev/test packages. See the [uv tool](uv.md) for details. +1. Development requirements file generated from `requirements.in`. Do not modify this file directly. Edit `requirements.in` instead. See the [uv tool](uv.md) for details. +1. AWS/production requirements source file. Contains only production dependencies — no dev or test packages. +1. AWS/production requirements file generated from `requirements-aws.in`. Do not modify this file directly. Edit `requirements-aws.in` instead. 1. Config file for development support tools such as `flake8` and `pytest`. `flake8` is the only tool that doesn't support `pyproject.toml` yet, which is why we have this file. #### Core App diff --git a/scripts/update-dependencies.sh b/scripts/update-dependencies.sh index 175714eb..5d9707b1 100755 --- a/scripts/update-dependencies.sh +++ b/scripts/update-dependencies.sh @@ -5,3 +5,6 @@ set -x # generate requirements.txt with the latest package versions docker compose exec web uv pip compile -o requirements.txt requirements.in --no-header --upgrade + +# generate requirements-aws.txt with the latest package versions +docker compose exec web uv pip compile -o requirements-aws.txt requirements-aws.in --no-header --upgrade --python-version 3.10