From 3253ea0d86c5f94a26f19cd38956a883522f56a5 Mon Sep 17 00:00:00 2001 From: Andrew Northall Date: Fri, 7 Feb 2025 06:22:32 +0000 Subject: [PATCH] refactor: working (new) docker compose config --- .dockerignore | 4 ++++ Dockerfile | 40 +++++++++++++++++------------------- conf/dev.env.dist | 2 ++ conf/run.sh | 51 +++++++++++++++++++--------------------------- conf/settings.py | 7 +++---- docker-compose.yml | 13 ++++++------ pyproject.toml | 1 + uv.lock | 20 ++++++++++++++++++ 8 files changed, 77 insertions(+), 61 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c986423 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.* +README.md +LICENCE +justfile diff --git a/Dockerfile b/Dockerfile index 85a5011..c435dd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,17 @@ # syntax=docker/dockerfile:1 FROM python:3.13-slim +COPY --from=ghcr.io/astral-sh/uv:0.5.29 /uv /uvx /bin/ -ARG UV_VERSION=0.5.11 +ENV UV_COMPILE_BYTECODE=1 +ENV UV_FROZEN=1 +ENV UV_LINK_MODE=copy +ENV UV_PROJECT_ENVIRONMENT=/app/.venv +ENV VIRTUAL_ENV=${UV_PROJECT_ENVIRONMENT} +ENV PYTHONPATH="/app/:/app/reportdb/" +ENV PATH="/app/.venv/bin:${PATH}" +ENV DJANGO_SETTINGS_MODULE=conf.settings -ENV PATH="/root/.local/bin:${PATH}" -ENV UV_PROJECT_ENVIRONMENT=/opt/uv/ -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -# Create directories -RUN mkdir -p /app /app/logs /app/src /opt/uv \ - /app/staticfiles /app/mediafiles - -ADD https://astral.sh/uv/${UV_VERSION}/install.sh /uv-installer.sh -RUN chmod +x /uv-installer.sh && /uv-installer.sh && rm /uv-installer.sh +RUN mkdir -p /app /opt/uv /app/staticfiles /app/mediafiles WORKDIR /app @@ -25,15 +23,15 @@ RUN apt-get update && apt-get install -y \ netcat-traditional \ tini -COPY ./pyproject.toml ./uv.lock /app/ -RUN groupadd app && useradd -g app -d /app app && chown app -R /app -RUN uv sync --frozen && chown app:app -R /opt/uv - -COPY etc/run.sh /app -RUN chmod +x /app/run.sh - -COPY ./reportdb/ /app/ +COPY uv.lock pyproject.toml /app/ +RUN uv sync --frozen +COPY . /app/ +RUN uv run /app/reportdb/manage.py collectstatic --noinput && \ + groupadd app && useradd -g app -d /app app && \ + chmod +x /app/conf/run.sh && \ + chown app -R /app USER app + ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["/app/run.sh", "start"] +CMD ["/app/conf/run.sh", "start"] diff --git a/conf/dev.env.dist b/conf/dev.env.dist index df83dd8..ffe11e6 100644 --- a/conf/dev.env.dist +++ b/conf/dev.env.dist @@ -2,6 +2,8 @@ # Development environment for Incident Database # +DJANGO_SETTINGS_MODULE = "conf.settings" + DEBUG = 1 SECRET_KEY = "django-insecure-c3+z5mi8bc3ah4@_eb11ure9l(6nip1mfri!_vgbe0k-8_*1_l" diff --git a/conf/run.sh b/conf/run.sh index b12693f..ff4807e 100644 --- a/conf/run.sh +++ b/conf/run.sh @@ -1,6 +1,6 @@ #!/bin/bash -CONF_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -PROJECT_ROOT="${CONF_ROOT}/.." +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +PROJECT_ROOT="$SCRIPT_DIR/.." APP_ROOT="${PROJECT_ROOT}/reportdb" if [ -z "$VIRTUAL_ENV" ] @@ -38,40 +38,31 @@ then fi cd "$PROJECT_ROOT" || exit 1 - echo "Starting server..." - # shellcheck disable=SC2030 - granian --interface wsgi \ - --host 0.0.0.0 \ - --port "${PORT:=8000}" \ - --workers 1 \ - --respawn-failed-workers \ - --reload \ - --reload-paths backend/src \ - --no-ws \ - --threading-mode workers \ - conf.wsgi:application & + gunicorn \ + --bind "0.0.0.0:$PORT" \ + --workers 2 \ + --reload \ + --reload-engine inotify \ + --name incidentdb-django \ + conf.wsgi:application fi # Run production server if [ "$1" = "start" ] then - cd "$APP_ROOT" || exit 1 + NUM_CORES=$(nproc) + NUM_WORKERS=$((NUM_CORES * 2 + 1)) + echo "Detected $NUM_CORES, running with $NUM_WORKERS workers" - echo "Collecting static files..." - python manage.py collectstatic --no-input - - cd "$PROJECT_ROOT" || exit 1 echo "Starting server..." - # shellcheck disable=SC2031 - granian --interface wsgi \ - --host 0.0.0.0 \ - --port "${PORT:=8000}" \ - --workers 4 \ - --respawn-failed-workers \ - --no-reload \ - --no-ws \ - --threading-mode workers \ - --workers-lifetime 600 \ - conf.wsgi:application & + gunicorn \ + --bind "0.0.0.0:$PORT" \ + --workers "$NUM_WORKERS" \ + --preload \ + --max-requests 1000 \ + --max-requests-jitter 200 \ + --keep-alive 10 \ + --name incidentdb-django \ + conf.wsgi:application fi diff --git a/conf/settings.py b/conf/settings.py index 19fd08e..8a82b25 100644 --- a/conf/settings.py +++ b/conf/settings.py @@ -8,9 +8,8 @@ django_stubs_ext.monkeypatch() -# Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - +DJANGO_ROOT = BASE_DIR / "reportdb" SECRET_KEY = os.environ.get("SECRET_KEY", "insecure-secret-key") ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "http://127.0.0.1").split() @@ -58,7 +57,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": ["templates"], + "DIRS": [DJANGO_ROOT / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -115,7 +114,7 @@ # Static and media files (CSS, JavaScript, Images) STATIC_URL = "/static/" STATIC_ROOT = os.environ.get("STATIC_ROOT", "/app/staticfiles") -STATICFILES_DIRS = [BASE_DIR / "static"] +STATICFILES_DIRS = [DJANGO_ROOT / "static"] AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME") AWS_S3_REGION_NAME = os.environ.get("AWS_S3_REGION_NAME") diff --git a/docker-compose.yml b/docker-compose.yml index 7817fa3..d5db942 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,8 @@ services: db: image: postgres:17 + volumes: + - postgres:/var/lib/postgresql/data environment: - POSTGRES_DB=postgres - POSTGRES_USER=postgres @@ -8,18 +10,17 @@ services: web: build: . - command: /app/run.sh devserver + command: /app/conf/run.sh devserver volumes: - - ./reportdb/:/app/src/ - - media:/app/mediafiles/ - - static:/app/staticfiles/ + - ./reportdb/:/app/reportdb/ ports: - "8000:8000" + environment: + - PORT=8000 env_file: - conf/dev.env depends_on: - db volumes: - media: - static: + postgres: diff --git a/pyproject.toml b/pyproject.toml index 68c12a1..f48abab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "django-storages[s3]", "gunicorn", "humanize", + "inotify>=0.2.10", "openai", "orjson", "Pillow", diff --git a/uv.lock b/uv.lock index d1014db..671a91a 100644 --- a/uv.lock +++ b/uv.lock @@ -101,6 +101,7 @@ dependencies = [ { name = "django-storages", extra = ["s3"] }, { name = "gunicorn" }, { name = "humanize" }, + { name = "inotify" }, { name = "openai" }, { name = "orjson" }, { name = "pillow" }, @@ -135,6 +136,7 @@ requires-dist = [ { name = "django-storages", extras = ["s3"] }, { name = "gunicorn" }, { name = "humanize" }, + { name = "inotify", specifier = ">=0.2.10" }, { name = "openai" }, { name = "orjson" }, { name = "pillow" }, @@ -424,6 +426,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "inotify" +version = "0.2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nose" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/cb/6d564f8a3f25d9516298dce151670d01e43a4b3b769c1c15f40453179cd5/inotify-0.2.10.tar.gz", hash = "sha256:974a623a338482b62e16d4eb705fb863ed33ec178680fc3e96ccdf0df6c02a07", size = 9905 } + [[package]] name = "jiter" version = "0.8.2" @@ -493,6 +504,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] +[[package]] +name = "nose" +version = "1.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/a5/0dc93c3ec33f4e281849523a5a913fa1eea9a3068acfa754d44d88107a44/nose-1.3.7.tar.gz", hash = "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98", size = 280488 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/d8/dd071918c040f50fa1cf80da16423af51ff8ce4a0f2399b7bf8de45ac3d9/nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", size = 154731 }, +] + [[package]] name = "openai" version = "1.61.0"