Docker compose refactor
Added shell interpolation for environment variables
This commit is contained in:
parent
e96c6f3528
commit
10affeb32f
16 changed files with 298 additions and 287 deletions
79
Dockerfile
Normal file
79
Dockerfile
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# stage 1
|
||||||
|
FROM python:3.11-alpine AS backend
|
||||||
|
|
||||||
|
ARG USER_UID=1000
|
||||||
|
ARG GROUP_UID=1000
|
||||||
|
|
||||||
|
RUN apk update \
|
||||||
|
&& apk add --no-cache \
|
||||||
|
vim \
|
||||||
|
curl \
|
||||||
|
gettext
|
||||||
|
|
||||||
|
RUN addgroup -g $USER_UID newsreader && adduser -Du $GROUP_UID -G newsreader newsreader
|
||||||
|
|
||||||
|
RUN mkdir --parents /app/src /app/logs /app/media /app/bin /app/static \
|
||||||
|
&& chown -R newsreader:newsreader /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
USER newsreader
|
||||||
|
|
||||||
|
COPY --chown=newsreader:newsreader uv.lock pyproject.toml /app/
|
||||||
|
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=$HOME/.cache/uv \
|
||||||
|
uv sync --frozen --no-default-groups --no-install-project
|
||||||
|
|
||||||
|
COPY --chown=newsreader:newsreader ./bin/docker-entrypoint.sh /app/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
VOLUME ["/app/logs", "/app/media", "/app/static"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# stage 2
|
||||||
|
FROM node:lts-alpine AS frontend-build
|
||||||
|
|
||||||
|
ARG BUILD_ARG=prod
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN chown node:node /app
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
COPY --chown=node:node ./package*.json ./webpack.*.js ./babel.config.js /app/
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=$HOME/.npm \
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
COPY --chown=node:node ./src /app/src
|
||||||
|
|
||||||
|
RUN npm run build:$BUILD_ARG
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# stage 3
|
||||||
|
FROM backend AS production
|
||||||
|
|
||||||
|
COPY --from=frontend-build --chown=newsreader:newsreader \
|
||||||
|
/app/src/newsreader/static /app/src/newsreader/static
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=$HOME/.cache/uv \
|
||||||
|
uv sync --frozen --only-group production --extra sentry
|
||||||
|
|
||||||
|
COPY --chown=newsreader:newsreader ./src /app/src
|
||||||
|
|
||||||
|
# Note that the static volume will have to be recreated to be pre-populated
|
||||||
|
# correctly with the latest static files. See
|
||||||
|
# https://docs.docker.com/storage/volumes/#populate-a-volume-using-a-container
|
||||||
|
RUN uv run --no-sync -- src/manage.py collectstatic --noinput
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# (optional) stage 4
|
||||||
|
FROM backend AS development
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=$HOME/.cache/uv \
|
||||||
|
uv sync --frozen --group development
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
/app/.venv/bin/python /app/src/manage.py migrate
|
uv run --no-sync -- /app/src/manage.py migrate
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,11 @@
|
||||||
volumes:
|
volumes:
|
||||||
static-files:
|
static-files:
|
||||||
node-modules:
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
celery:
|
|
||||||
build:
|
|
||||||
target: development
|
|
||||||
volumes:
|
|
||||||
- ./src/:/app/src
|
|
||||||
|
|
||||||
django:
|
django:
|
||||||
build:
|
build: &app-development-build
|
||||||
target: development
|
target: development
|
||||||
command: /app/.venv/bin/python /app/src/manage.py runserver 0.0.0.0:8000
|
command: uv run --no-sync -- /app/src/manage.py runserver 0.0.0.0:8000
|
||||||
ports:
|
ports:
|
||||||
- "${DJANGO_PORT:-8000}:8000"
|
- "${DJANGO_PORT:-8000}:8000"
|
||||||
volumes:
|
volumes:
|
||||||
|
|
@ -21,12 +14,19 @@ services:
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
|
|
||||||
|
celery:
|
||||||
|
build:
|
||||||
|
<<: *app-development-build
|
||||||
|
volumes:
|
||||||
|
- ./src/:/app/src
|
||||||
|
|
||||||
webpack:
|
webpack:
|
||||||
build:
|
build:
|
||||||
|
target: frontend-build
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./docker/webpack
|
args:
|
||||||
|
BUILD_ARG: "dev"
|
||||||
command: npm run build:watch
|
command: npm run build:watch
|
||||||
volumes:
|
volumes:
|
||||||
- ./src/:/app/src
|
- ./src/:/app/src
|
||||||
- static-files:/app/src/newsreader/static
|
- static-files:/app/src/newsreader/static
|
||||||
- node-modules:/app/node_modules
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ services:
|
||||||
django:
|
django:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
# Note that --env-file should be used to set these correctly
|
|
||||||
- "${NGINX_HTTP_PORT:-80}:80"
|
- "${NGINX_HTTP_PORT:-80}:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/nginx/conf.d:/etc/nginx/conf.d
|
- ./config/nginx/conf.d:/etc/nginx/conf.d
|
||||||
|
|
|
||||||
|
|
@ -4,33 +4,43 @@ volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
static-files:
|
static-files:
|
||||||
|
|
||||||
x-db-env: &db-env
|
x-db-connection-env: &db-connection-env
|
||||||
POSTGRES_HOST:
|
POSTGRES_HOST: ${POSTGRES_HOST:-db}
|
||||||
POSTGRES_PORT:
|
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
||||||
POSTGRES_DB:
|
POSTGRES_DB: &pg-database ${POSTGRES_DB:-newsreader}
|
||||||
POSTGRES_USER:
|
POSTGRES_USER: &pg-user ${POSTGRES_USER:-newsreader}
|
||||||
POSTGRES_PASSWORD:
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-newsreader}
|
||||||
|
|
||||||
x-django-build-env: &django-build-env
|
x-db-env: &db-env
|
||||||
<<: *db-env
|
<<: *db-connection-env
|
||||||
DJANGO_SECRET_KEY:
|
PGUSER: *pg-user
|
||||||
DJANGO_SETTINGS_MODULE:
|
PGDATABASE: *pg-database
|
||||||
|
|
||||||
x-django-env: &django-env
|
x-django-env: &django-env
|
||||||
<<: *django-build-env
|
<<: *db-connection-env
|
||||||
VERSION:
|
|
||||||
|
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,django}
|
||||||
|
INTERNAL_IPS: ${INTERNAL_IPS:-localhost,127.0.0.1,django}
|
||||||
|
|
||||||
|
# see token_urlsafe from python's secret module to generate one
|
||||||
|
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-Ojg68lYsP3kq2r5JgozUzKVSRFywm17BTMS5iwpLM44}
|
||||||
|
DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE:-newsreader.conf.production}
|
||||||
|
|
||||||
|
ADMINS: ${ADMINS:-""}
|
||||||
|
|
||||||
|
VERSION: ${VERSION:-""}
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
EMAIL_HOST:
|
EMAIL_HOST: ${EMAIL_HOST:-localhost}
|
||||||
EMAIL_PORT:
|
EMAIL_PORT: ${EMAIL_PORT:-25}
|
||||||
EMAIL_HOST_USER:
|
EMAIL_HOST_USER: ${EMAIL_HOST_USER:-""}
|
||||||
EMAIL_HOST_PASSWORD:
|
EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD:-""}
|
||||||
EMAIL_USE_TLS:
|
EMAIL_USE_TLS: ${EMAIL_USE_TLS:-no}
|
||||||
EMAIL_USE_SSL:
|
EMAIL_USE_SSL: ${EMAIL_USE_SSL:-no}
|
||||||
EMAIL_DEFAULT_FROM:
|
EMAIL_DEFAULT_FROM: ${EMAIL_DEFAULT_FROM:-webmaster@localhost}
|
||||||
|
|
||||||
# Sentry
|
# Sentry
|
||||||
SENTRY_DSN:
|
SENTRY_DSN: ${SENTRY_DSN:-""}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
|
|
@ -38,8 +48,8 @@ services:
|
||||||
<<: *db-env
|
<<: *db-env
|
||||||
image: postgres:15
|
image: postgres:15
|
||||||
healthcheck:
|
healthcheck:
|
||||||
# Note that --env-file should be used to set these correctly
|
test: /usr/bin/pg_isready
|
||||||
test: /usr/bin/pg_isready --username="${POSTGRES_USER}" --dbname="${POSTGRES_DB}"
|
start_period: 10s
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
@ -55,58 +65,23 @@ services:
|
||||||
- memcached
|
- memcached
|
||||||
- -m 64
|
- -m 64
|
||||||
|
|
||||||
celery:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./docker/django
|
|
||||||
target: production
|
|
||||||
args:
|
|
||||||
<<: *django-build-env
|
|
||||||
environment:
|
|
||||||
<<: *django-env
|
|
||||||
command: |
|
|
||||||
/app/.venv/bin/celery --app newsreader
|
|
||||||
--workdir /app/src/
|
|
||||||
worker --loglevel INFO
|
|
||||||
--concurrency 2
|
|
||||||
--beat
|
|
||||||
--scheduler django
|
|
||||||
-n worker1@%h
|
|
||||||
-n worker2@%h
|
|
||||||
healthcheck:
|
|
||||||
test: celery --app newsreader status || exit 1
|
|
||||||
interval: 10s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
depends_on:
|
|
||||||
rabbitmq:
|
|
||||||
condition: service_started
|
|
||||||
memcached:
|
|
||||||
condition: service_started
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
django:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
- logs:/app/logs
|
|
||||||
|
|
||||||
django:
|
django:
|
||||||
build:
|
build: &app-build
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./docker/django
|
|
||||||
target: production
|
target: production
|
||||||
args:
|
|
||||||
<<: *django-build-env
|
|
||||||
environment:
|
environment:
|
||||||
<<: *django-env
|
<<: *django-env
|
||||||
entrypoint: /app/bin/docker-entrypoint.sh
|
entrypoint: ["/bin/sh", "/app/bin/docker-entrypoint.sh"]
|
||||||
command: |
|
command: |
|
||||||
/app/.venv/bin/gunicorn --bind 0.0.0.0:8000
|
uv run --no-sync --
|
||||||
|
gunicorn
|
||||||
|
--bind 0.0.0.0:8000
|
||||||
--workers 3
|
--workers 3
|
||||||
--chdir /app/src/
|
--chdir /app/src/
|
||||||
newsreader.wsgi:application
|
newsreader.wsgi:application
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: /usr/bin/curl --fail http://django:8000 || exit 1
|
test: /usr/bin/curl --fail http://django:8000 || exit 1
|
||||||
|
start_period: 10s
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
@ -119,3 +94,33 @@ services:
|
||||||
- logs:/app/logs
|
- logs:/app/logs
|
||||||
- media:/app/media
|
- media:/app/media
|
||||||
- static-files:/app/static
|
- static-files:/app/static
|
||||||
|
|
||||||
|
celery:
|
||||||
|
build:
|
||||||
|
<<: *app-build
|
||||||
|
environment:
|
||||||
|
<<: *django-env
|
||||||
|
command: |
|
||||||
|
uv run --no-sync --
|
||||||
|
celery
|
||||||
|
--app newsreader
|
||||||
|
--workdir /app/src/
|
||||||
|
worker --loglevel INFO
|
||||||
|
--concurrency 2
|
||||||
|
--beat
|
||||||
|
--scheduler django
|
||||||
|
-n worker1@%h
|
||||||
|
-n worker2@%h
|
||||||
|
healthcheck:
|
||||||
|
test: uv run --no-sync -- celery --app newsreader status || exit 1
|
||||||
|
start_period: 10s
|
||||||
|
interval: 10s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
depends_on:
|
||||||
|
rabbitmq:
|
||||||
|
condition: service_started
|
||||||
|
django:
|
||||||
|
condition: service_healthy
|
||||||
|
volumes:
|
||||||
|
- logs:/app/logs
|
||||||
|
|
|
||||||
102
docker/django
102
docker/django
|
|
@ -1,102 +0,0 @@
|
||||||
# stage 1
|
|
||||||
FROM python:3.11-bookworm AS backend
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --yes --no-install-recommends \
|
|
||||||
vim \
|
|
||||||
curl \
|
|
||||||
gettext \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
RUN mkdir /app/src
|
|
||||||
RUN mkdir /app/logs
|
|
||||||
RUN mkdir /app/media
|
|
||||||
|
|
||||||
COPY uv.lock pyproject.toml /app/
|
|
||||||
|
|
||||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
|
||||||
RUN uv sync --frozen --no-default-groups --no-install-project
|
|
||||||
|
|
||||||
|
|
||||||
# stage 2
|
|
||||||
FROM node:lts AS frontend-build
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY ./*.json ./*.js ./babel.config.js /app/
|
|
||||||
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
COPY ./src /app/src
|
|
||||||
|
|
||||||
RUN npm run build:prod
|
|
||||||
|
|
||||||
|
|
||||||
# stage 3
|
|
||||||
FROM python:3.11-bookworm AS production
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --yes --no-install-recommends \
|
|
||||||
postgresql-client \
|
|
||||||
vim \
|
|
||||||
curl \
|
|
||||||
gettext \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN mkdir /app/logs
|
|
||||||
RUN mkdir /app/media
|
|
||||||
RUN mkdir /app/bin
|
|
||||||
|
|
||||||
COPY --from=backend /app/.venv /app/.venv
|
|
||||||
COPY --from=backend /app/uv.lock /app/pyproject.toml /app/
|
|
||||||
COPY --from=backend /bin/uv /bin/uv
|
|
||||||
|
|
||||||
COPY ./bin/docker-entrypoint.sh /app/bin/docker-entrypoint.sh
|
|
||||||
|
|
||||||
COPY --from=frontend-build /app/src/newsreader/static /app/src/newsreader/static
|
|
||||||
|
|
||||||
COPY ./src /app/src
|
|
||||||
|
|
||||||
RUN uv sync --frozen --only-group production --extra sentry
|
|
||||||
|
|
||||||
RUN useradd --no-create-home --uid 1000 newsreader
|
|
||||||
RUN chown --recursive newsreader:newsreader /app
|
|
||||||
|
|
||||||
USER newsreader
|
|
||||||
|
|
||||||
ARG POSTGRES_HOST
|
|
||||||
ARG POSTGRES_PORT
|
|
||||||
ARG POSTGRES_DB
|
|
||||||
ARG POSTGRES_USER
|
|
||||||
ARG POSTGRES_PASSWORD
|
|
||||||
ARG DJANGO_SECRET_KEY
|
|
||||||
ARG DJANGO_SETTINGS_MODULE
|
|
||||||
|
|
||||||
# Note that the static volume will have to be recreated to be pre-populated
|
|
||||||
# correctly with the latest static files. See
|
|
||||||
# https://docs.docker.com/storage/volumes/#populate-a-volume-using-a-container
|
|
||||||
RUN /app/.venv/bin/python src/manage.py collectstatic --noinput
|
|
||||||
|
|
||||||
|
|
||||||
# (optional) stage 4
|
|
||||||
FROM python:3.11-bookworm AS development
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --yes --no-install-recommends \
|
|
||||||
vim \
|
|
||||||
curl \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN mkdir /app/logs
|
|
||||||
RUN mkdir /app/media
|
|
||||||
RUN mkdir /app/bin
|
|
||||||
|
|
||||||
COPY --from=backend /app/.venv /app/.venv
|
|
||||||
COPY --from=backend /app/uv.lock /app/pyproject.toml /app/
|
|
||||||
COPY ./bin/docker-entrypoint.sh /app/bin/docker-entrypoint.sh
|
|
||||||
COPY --from=backend /app/src/ /app/src/
|
|
||||||
|
|
||||||
COPY --from=backend /bin/uv /bin/uv
|
|
||||||
RUN uv sync --frozen --group development
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
FROM node:lts
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
RUN mkdir /app/src
|
|
||||||
|
|
||||||
COPY package*.json webpack.*.js babel.config.js /app/
|
|
||||||
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
COPY ./src /app/src
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "npx prettier \"src/newsreader/js/**/*.js\" --check",
|
"lint": "npx prettier \"src/newsreader/js/**/*.js\" --check",
|
||||||
"format": "npx prettier \"src/newsreader/js/**/*.js\" --write",
|
"format": "npx prettier \"src/newsreader/js/**/*.js\" --write",
|
||||||
"build": "npx webpack --config webpack.dev.babel.js",
|
|
||||||
"build:watch": "npx webpack --config webpack.dev.babel.js --watch",
|
"build:watch": "npx webpack --config webpack.dev.babel.js --watch",
|
||||||
|
"build:dev": "npx webpack --config webpack.dev.babel.js",
|
||||||
"build:prod": "npx webpack --config webpack.prod.babel.js",
|
"build:prod": "npx webpack --config webpack.prod.babel.js",
|
||||||
"test": "npx jest",
|
"test": "npx jest",
|
||||||
"test:watch": "npm test -- --watch"
|
"test:watch": "npm test -- --watch"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"django~=4.2",
|
"django~=4.2",
|
||||||
"celery~=5.4",
|
"celery~=5.4",
|
||||||
"psycopg",
|
"psycopg[binary]",
|
||||||
"django-axes",
|
"django-axes",
|
||||||
"django-celery-beat~=2.7.0",
|
"django-celery-beat~=2.7.0",
|
||||||
"django-rest-framework",
|
"django-rest-framework",
|
||||||
|
|
@ -36,7 +36,7 @@ production = ["gunicorn~=23.0"]
|
||||||
sentry = ["sentry-sdk~=2.0"]
|
sentry = ["sentry-sdk~=2.0"]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
environments = ["sys_platform == "linux""]
|
environments = ["sys_platform == 'linux'"]
|
||||||
default-groups = ["test-tools"]
|
default-groups = ["test-tools"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
|
@ -68,7 +68,7 @@ django = ["django"]
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
source = ["./src/newsreader/"]
|
source = ["./src/newsreader/"]
|
||||||
omit = [
|
omit = [
|
||||||
"**/tests/**"
|
"**/tests/**",
|
||||||
"**/migrations/**",
|
"**/migrations/**",
|
||||||
"**/conf/**",
|
"**/conf/**",
|
||||||
"**/apps.py",
|
"**/apps.py",
|
||||||
|
|
@ -77,5 +77,5 @@ omit = [
|
||||||
"**/urls.py",
|
"**/urls.py",
|
||||||
"**/wsgi.py",
|
"**/wsgi.py",
|
||||||
"**/celery.py",
|
"**/celery.py",
|
||||||
"**/__init__.py
|
"**/__init__.py"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ from pathlib import Path
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from newsreader.conf.utils import get_env, get_root_dir
|
||||||
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
@ -15,16 +17,13 @@ except ImportError:
|
||||||
DjangoIntegration = None
|
DjangoIntegration = None
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent
|
BASE_DIR = get_root_dir()
|
||||||
DJANGO_PROJECT_DIR = BASE_DIR / "src" / "newsreader"
|
DJANGO_PROJECT_DIR = BASE_DIR / "src" / "newsreader"
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
|
||||||
# SECURITY WARNING: don"t run with debug turned on in production!
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
|
ALLOWED_HOSTS = get_env("ALLOWED_HOSTS", split=",", default=["127.0.0.1", "localhost"])
|
||||||
INTERNAL_IPS = ["127.0.0.1", "localhost"]
|
INTERNAL_IPS = get_env("INTERNAL_IPS", split=",", default=["127.0.0.1", "localhost"])
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
|
@ -48,7 +47,7 @@ INSTALLED_APPS = [
|
||||||
"newsreader.news.collection",
|
"newsreader.news.collection",
|
||||||
]
|
]
|
||||||
|
|
||||||
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
|
SECRET_KEY = get_env("DJANGO_SECRET_KEY", default="")
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
"axes.backends.AxesBackend",
|
"axes.backends.AxesBackend",
|
||||||
|
|
@ -73,11 +72,10 @@ FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")],
|
"DIRS": [DJANGO_PROJECT_DIR / "templates"],
|
||||||
"APP_DIRS": True,
|
"APP_DIRS": True,
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"context_processors": [
|
"context_processors": [
|
||||||
"django.template.context_processors.debug",
|
|
||||||
"django.template.context_processors.request",
|
"django.template.context_processors.request",
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
|
@ -88,16 +86,14 @@ TEMPLATES = [
|
||||||
|
|
||||||
WSGI_APPLICATION = "newsreader.wsgi.application"
|
WSGI_APPLICATION = "newsreader.wsgi.application"
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"HOST": os.environ["POSTGRES_HOST"],
|
"HOST": get_env("POSTGRES_HOST", default=""),
|
||||||
"PORT": os.environ["POSTGRES_PORT"],
|
"PORT": get_env("POSTGRES_PORT", default=""),
|
||||||
"NAME": os.environ["POSTGRES_DB"],
|
"NAME": get_env("POSTGRES_DB", default=""),
|
||||||
"USER": os.environ["POSTGRES_USER"],
|
"USER": get_env("POSTGRES_USER", default=""),
|
||||||
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
|
"PASSWORD": get_env("POSTGRES_PASSWORD", default=""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,8 +110,6 @@ CACHES = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Logging
|
|
||||||
# https://docs.djangoproject.com/en/2.2/topics/logging/#configuring-logging
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"disable_existing_loggers": False,
|
"disable_existing_loggers": False,
|
||||||
|
|
@ -169,8 +163,6 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
|
||||||
|
|
@ -185,8 +177,6 @@ AUTH_USER_MODEL = "accounts.User"
|
||||||
|
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = "Europe/Amsterdam"
|
TIME_ZONE = "Europe/Amsterdam"
|
||||||
|
|
@ -194,20 +184,33 @@ USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
STATIC_ROOT = BASE_DIR / "static"
|
||||||
STATICFILES_DIRS = [os.path.join(DJANGO_PROJECT_DIR, "static")]
|
STATICFILES_DIRS = (
|
||||||
|
DJANGO_PROJECT_DIR / "static",
|
||||||
|
)
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-STATICFILES_FINDERS
|
|
||||||
STATICFILES_FINDERS = [
|
STATICFILES_FINDERS = [
|
||||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
|
|
||||||
|
DEFAULT_FROM_EMAIL = get_env(
|
||||||
|
"EMAIL_DEFAULT_FROM", required=False, default="webmaster@localhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
EMAIL_HOST = get_env("EMAIL_HOST", required=False, default="localhost")
|
||||||
|
EMAIL_PORT = get_env("EMAIL_PORT", cast=int, required=False, default=25)
|
||||||
|
|
||||||
|
EMAIL_HOST_USER = get_env("EMAIL_HOST_USER", required=False, default="")
|
||||||
|
EMAIL_HOST_PASSWORD = get_env("EMAIL_HOST_PASSWORD", required=False, default="")
|
||||||
|
|
||||||
|
EMAIL_USE_TLS = get_env("EMAIL_USE_TLS", required=False, default=False)
|
||||||
|
EMAIL_USE_SSL = get_env("EMAIL_USE_SSL", required=False, default=False)
|
||||||
|
|
||||||
|
|
||||||
# Third party settings
|
# Third party settings
|
||||||
AXES_HANDLER = "axes.handlers.cache.AxesCacheHandler"
|
AXES_HANDLER = "axes.handlers.cache.AxesCacheHandler"
|
||||||
|
|
@ -216,7 +219,6 @@ AXES_FAILURE_LIMIT = 5
|
||||||
AXES_COOLOFF_TIME = 3 # in hours
|
AXES_COOLOFF_TIME = 3 # in hours
|
||||||
AXES_RESET_ON_SUCCESS = True
|
AXES_RESET_ON_SUCCESS = True
|
||||||
|
|
||||||
# TODO: verify parser works correctly
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
|
|
@ -247,7 +249,7 @@ CELERY_BROKER_URL = "amqp://guest@rabbitmq:5672"
|
||||||
|
|
||||||
# Sentry
|
# Sentry
|
||||||
SENTRY_CONFIG = {
|
SENTRY_CONFIG = {
|
||||||
"dsn": os.environ.get("SENTRY_DSN"),
|
"dsn": get_env("SENTRY_DSN", default="", required=False),
|
||||||
"send_default_pii": False,
|
"send_default_pii": False,
|
||||||
"integrations": [DjangoIntegration(), CeleryIntegration()]
|
"integrations": [DjangoIntegration(), CeleryIntegration()]
|
||||||
if DjangoIntegration and CeleryIntegration
|
if DjangoIntegration and CeleryIntegration
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from .base import * # noqa: F403
|
from .base import * # noqa: F403
|
||||||
from .version import get_current_version
|
from .utils import get_current_version
|
||||||
|
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from .base import * # noqa: F403
|
from .base import * # noqa: F403
|
||||||
from .version import get_current_version
|
from .utils import get_current_version
|
||||||
|
|
||||||
|
|
||||||
SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl"
|
SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl"
|
||||||
|
|
@ -10,6 +10,11 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
|
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
|
||||||
|
|
||||||
|
|
||||||
|
TEMPLATES[0]["OPTIONS"]["context_processors"].append(
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
)
|
||||||
|
|
||||||
# Project settings
|
# Project settings
|
||||||
VERSION = get_current_version()
|
VERSION = get_current_version()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from .base import * # noqa: F403
|
from .base import * # noqa: F403
|
||||||
from .version import get_current_version
|
from .utils import get_current_version
|
||||||
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["django", "127.0.0.1"]
|
DEBUG = True
|
||||||
|
|
||||||
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
|
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
|
||||||
|
|
||||||
|
|
@ -16,7 +16,10 @@ LOGGING["loggers"].update( # noqa: F405
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
|
TEMPLATES[0]["OPTIONS"]["context_processors"].append(
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
)
|
||||||
|
|
||||||
# Project settings
|
# Project settings
|
||||||
VERSION = get_current_version()
|
VERSION = get_current_version()
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,20 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from newsreader.conf.utils import get_env
|
||||||
|
|
||||||
from .base import * # noqa: F403
|
from .base import * # noqa: F403
|
||||||
from .version import get_current_version
|
from .utils import get_current_version
|
||||||
|
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["127.0.0.1", "localhost", "rss.fudiggity.nl", "django"]
|
|
||||||
|
|
||||||
ADMINS = [
|
ADMINS = [
|
||||||
("", email)
|
("", email)
|
||||||
for email in os.getenv("ADMINS", "").split(",")
|
for email in get_env("ADMINS", split=",", required=False, default=[])
|
||||||
if os.environ.get("ADMINS")
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
||||||
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")], # noqa: F405
|
|
||||||
"APP_DIRS": True,
|
|
||||||
"OPTIONS": {
|
|
||||||
"context_processors": [
|
|
||||||
"django.template.context_processors.request",
|
|
||||||
"django.contrib.auth.context_processors.auth",
|
|
||||||
"django.contrib.messages.context_processors.messages",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Email
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
|
||||||
DEFAULT_FROM_EMAIL = os.environ.get("EMAIL_DEFAULT_FROM", "webmaster@localhost")
|
|
||||||
|
|
||||||
EMAIL_HOST = os.environ.get("EMAIL_HOST", "localhost")
|
|
||||||
EMAIL_PORT = os.environ.get("EMAIL_PORT", 25)
|
|
||||||
|
|
||||||
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
|
|
||||||
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
|
|
||||||
|
|
||||||
EMAIL_USE_TLS = bool(os.environ.get("EMAIL_USE_TLS"))
|
|
||||||
EMAIL_USE_SSL = bool(os.environ.get("EMAIL_USE_SSL"))
|
|
||||||
|
|
||||||
# Project settings
|
# Project settings
|
||||||
VERSION = get_current_version(debug=False)
|
VERSION = get_current_version(debug=False)
|
||||||
ENVIRONMENT = "production"
|
ENVIRONMENT = "production"
|
||||||
|
|
|
||||||
85
src/newsreader/conf/utils.py
Normal file
85
src/newsreader/conf/utils.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Iterable, Type
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_env(
|
||||||
|
name: str,
|
||||||
|
cast: Type = str,
|
||||||
|
required: bool = True,
|
||||||
|
default: Any = None,
|
||||||
|
split: str = ""
|
||||||
|
) -> Any:
|
||||||
|
if cast is not str and split:
|
||||||
|
raise TypeError(f"Split is not possible with {cast}")
|
||||||
|
|
||||||
|
value = os.getenv(name)
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
if required:
|
||||||
|
logger.warning(f"Missing environment variable: {name}")
|
||||||
|
|
||||||
|
return default
|
||||||
|
|
||||||
|
bool_mapping = {"yes": True, "true": True, "false": False, "no": False}
|
||||||
|
|
||||||
|
if cast is bool:
|
||||||
|
_value = bool_mapping.get(value.lower())
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
raise ValueError(f"Unknown boolean value: {_value}")
|
||||||
|
|
||||||
|
return _value
|
||||||
|
|
||||||
|
value = value if not cast else cast(value)
|
||||||
|
return value if not split else value.split(split)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_version(debug: bool = True) -> str:
|
||||||
|
version = get_env("VERSION", required=False)
|
||||||
|
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
|
||||||
|
if debug:
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(
|
||||||
|
["git", "show", "--no-patch", "--format=%H"], universal_newlines=True
|
||||||
|
)
|
||||||
|
return output.strip()
|
||||||
|
except (subprocess.CalledProcessError, OSError):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(
|
||||||
|
["git", "describe", "--tags"], universal_newlines=True
|
||||||
|
)
|
||||||
|
return output.strip()
|
||||||
|
except (subprocess.CalledProcessError, OSError):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_MARKERS = ("pyproject.toml", "package.json", "README.md", "CHANGELOG.md")
|
||||||
|
|
||||||
|
|
||||||
|
def get_root_dir() -> Path:
|
||||||
|
file = Path(__file__)
|
||||||
|
return _traverse_dirs(file.parent, ROOT_MARKERS)
|
||||||
|
|
||||||
|
|
||||||
|
def _traverse_dirs(path: Path, root_markers: Iterable[str]) -> Path:
|
||||||
|
if path.parent == path:
|
||||||
|
raise OSError("Root directory detected")
|
||||||
|
|
||||||
|
files = (file.name for file in path.iterdir())
|
||||||
|
|
||||||
|
if not any((marker for marker in root_markers if marker in files)):
|
||||||
|
return _traverse_dirs(path.parent, root_markers)
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_version(debug=True):
|
|
||||||
version = os.environ.get("VERSION")
|
|
||||||
|
|
||||||
if version:
|
|
||||||
return version
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
try:
|
|
||||||
output = subprocess.check_output(
|
|
||||||
["git", "show", "--no-patch", "--format=%H"], universal_newlines=True
|
|
||||||
)
|
|
||||||
return output.strip()
|
|
||||||
except (subprocess.CalledProcessError, OSError):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
output = subprocess.check_output(
|
|
||||||
["git", "describe", "--tags"], universal_newlines=True
|
|
||||||
)
|
|
||||||
return output.strip()
|
|
||||||
except (subprocess.CalledProcessError, OSError):
|
|
||||||
return ""
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue