From ffbb100d6c69e49523c5747b978b39bb9dd8ccfd Mon Sep 17 00:00:00 2001 From: Keith James Date: Fri, 14 Jun 2024 15:23:26 +0100 Subject: [PATCH] Add a development Dockerfile --- docker/Dockerfile.dev | 99 ++++++++++++++++++++ docker/README.md | 19 ++++ docker/logging.json | 51 ++++++++++ docker/scripts/configure_database_service.sh | 12 +++ docker/scripts/create_database.sh | 30 ++++++ docker/scripts/entrypoint.sh | 16 ++++ docker/scripts/insert_admin_token.sh | 18 ++++ scripts/deploy_schema.py | 2 + 8 files changed, 247 insertions(+) create mode 100644 docker/Dockerfile.dev create mode 100644 docker/README.md create mode 100644 docker/logging.json create mode 100755 docker/scripts/configure_database_service.sh create mode 100755 docker/scripts/create_database.sh create mode 100755 docker/scripts/entrypoint.sh create mode 100755 docker/scripts/insert_admin_token.sh mode change 100644 => 100755 scripts/deploy_schema.py diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev new file mode 100644 index 0000000..cf4f678 --- /dev/null +++ b/docker/Dockerfile.dev @@ -0,0 +1,99 @@ + +ARG BASE_IMAGE=python:3.10-slim +FROM $BASE_IMAGE as builder + +ARG DEBIAN_FRONTEND="noninteractive" + +RUN apt-get update && \ + apt-get install -q -y --no-install-recommends \ + build-essential \ + gcc \ + libsqlite3-dev \ + unattended-upgrades && \ + unattended-upgrade -v + +WORKDIR /app + +COPY .. . + +RUN python -m venv /app && \ + . ./bin/activate && \ + pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir . + + +FROM $BASE_IMAGE + +ARG DEBIAN_FRONTEND + +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -q -y --no-install-recommends \ + libsqlite3-0 \ + postgresql \ + sudo \ + tini \ + locales && \ + locale-gen en_GB en_GB.UTF-8 && \ + localedef -i en_GB -c -f UTF-8 -A /usr/share/locale/locale.alias en_GB.UTF-8 + +RUN apt-get install -q -y --no-install-recommends \ + unattended-upgrades && \ + unattended-upgrade -v && \ + apt-get remove -q -y unattended-upgrades && \ + apt-get autoremove -q -y && \ + apt-get clean -q -y && \ + rm -rf /var/lib/apt/lists/* + +ENV LANG=en_GB.UTF-8 \ + LANGUAGE=en_GB \ + LC_ALL=en_GB.UTF-8 \ + TZ="Etc/UTC" + +ARG APP_USER=appuser +ARG APP_UID=1000 +ARG APP_GID=$APP_UID + +WORKDIR /app + +RUN groupadd --gid $APP_GID $APP_USER && \ + useradd --uid $APP_UID --gid $APP_GID --shell /bin/bash --create-home $APP_USER + +COPY --from=builder --chown=$APP_USER:$APP_GID /app /app + +ARG DB_HOST=localhost +ARG DB_PORT=5432 +ARG DB_SCHEMA=porch_dev +ARG DB_NAME=porch_dev_db +ARG DB_USER=porch_admin +ARG DB_PASS=porch +ARG URL_SLUG="$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME" + +ENV DB_HOST=$DB_HOST \ + DB_PORT=$DB_PORT \ + DB_SCHEMA=$DB_SCHEMA \ + DB_NAME=$DB_NAME \ + DB_USER=$DB_USER \ + DB_PASS=$DB_PASS \ + DB_URL="postgresql+psycopg2://$URL_SLUG" + +RUN service postgresql start && \ + /app/docker/scripts/create_database.sh && \ + /app/docker/scripts/configure_database_service.sh && \ + . /app/bin/activate && \ + /app/scripts/deploy_schema.py && \ + /app/docker/scripts/insert_admin_token.sh && \ + service postgresql stop + +USER $APP_USER + +ARG PORT=8081 + +ENV DB_URL="postgresql+asyncpg://$URL_SLUG" \ + PORT=${PORT} + +EXPOSE ${PORT} + +ENTRYPOINT ["/usr/bin/tini", "--"] + +CMD ["/app/docker/scripts/entrypoint.sh"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..73b0fe2 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,19 @@ +# Development Dockerfile + +The Dockerfile and scripts in this directory may be use to create a development image +that hosts both the PostgreSQL database and the npg_porch server. + +The application is populated with a hard-coded administrator user, password and +administration token and is configured log to STDERR and STDOUT. + +To create an image using the Dockerfile, run the following command from the root of the +repository: + +```bash +docker build --rm -f docker/Dockerfile.dev -t npg_porch_dev . +``` + +The Dockerfile supports a number of arguments that can be passed to the `docker build` +command to configure most aspects of the application, including user names, passwords, +database names and ports. However, the default values should be suitable for most +needs. diff --git a/docker/logging.json b/docker/logging.json new file mode 100644 index 0000000..53723a0 --- /dev/null +++ b/docker/logging.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "formatters": { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": "%(levelprefix)s %(message)s", + "use_colors": null + }, + "access": { + "()": "uvicorn.logging.AccessFormatter", + "fmt": "%(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s" + } + }, + "handlers": { + "stderr": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr" + }, + "stdout": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout" + }, + "access": { + "formatter": "access", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout" + } + }, + "loggers": { + "uvicorn": { + "handlers": ["access"], + "level": "INFO", + "propagate": false + }, + "uvicorn.error": { + "handlers": ["stderr"], + "level": "DEBUG", + "propagate": false + }, + "fastapi": { + "handlers": ["stderr"], + "level": "INFO" + } + }, + "root": { + "handlers": ["stdout"], + "level": "DEBUG" + } +} diff --git a/docker/scripts/configure_database_service.sh b/docker/scripts/configure_database_service.sh new file mode 100755 index 0000000..7df868c --- /dev/null +++ b/docker/scripts/configure_database_service.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -eo pipefail +set -x + +APP_USER=${APP_USER:? The APP_USER environment variable must be set} + +cat > "/etc/sudoers.d/$APP_USER" << EOF +$APP_USER ALL= NOPASSWD: /usr/sbin/service postgresql start +$APP_USER ALL= NOPASSWD: /usr/sbin/service postgresql restart +$APP_USER ALL= NOPASSWD: /usr/sbin/service postgresql stop +EOF diff --git a/docker/scripts/create_database.sh b/docker/scripts/create_database.sh new file mode 100755 index 0000000..52d094b --- /dev/null +++ b/docker/scripts/create_database.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -eo pipefail +set -x + +pg_isready --quiet || { + echo "PostgreSQL is not ready" >&2 + exit 1 +} + +DB_SCHEMA=${DB_SCHEMA:? The DB_SCHEMA environment variable must be set} +DB_NAME=${DB_NAME:? The DB_NAME environment variable must be set} +DB_USER=${DB_USER:? The DB_USER environment variable must be set} +DB_PASS=${DB_PASS:? The DB_PASS environment variable must be set} + +sudo -u postgres createuser -D -R -S ${DB_USER} +sudo -u postgres createdb -O ${DB_USER} ${DB_NAME} + +sudo -u postgres psql -d ${DB_NAME} << EOF +ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}'; + +CREATE SCHEMA ${DB_SCHEMA}; + +SET search_path TO ${DB_SCHEMA}, public; + +GRANT ALL PRIVILEGES ON SCHEMA ${DB_SCHEMA} TO ${DB_USER}; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ${DB_SCHEMA} TO ${DB_USER}; +GRANT USAGE ON ALL SEQUENCES IN SCHEMA ${DB_SCHEMA} TO ${DB_USER}; +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ${DB_SCHEMA} TO ${DB_USER}; +EOF diff --git a/docker/scripts/entrypoint.sh b/docker/scripts/entrypoint.sh new file mode 100755 index 0000000..777f43a --- /dev/null +++ b/docker/scripts/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -eo pipefail + +sudo service postgresql start + +pg_isready --quiet --timeout=30 || { + echo "PostgreSQL is not ready" >&2 + exit 1 +} + +PORT=${PORT:? The PORT environment variable must be set} + +source /app/bin/activate + +uvicorn npg_porch.server:app --host 0.0.0.0 --port ${PORT} --reload --log-config /app/docker/logging.json diff --git a/docker/scripts/insert_admin_token.sh b/docker/scripts/insert_admin_token.sh new file mode 100755 index 0000000..34ef944 --- /dev/null +++ b/docker/scripts/insert_admin_token.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eo pipefail +set -x + +pg_isready --quiet || { + echo "PostgreSQL is not ready" >&2 + exit 1 +} + +DB_SCHEMA=${DB_SCHEMA:? The DB_SCHEMA environment variable must be set} +DB_NAME=${DB_NAME:? The DB_NAME environment variable must be set} + +ADMIN_TOKEN=${ADMIN_TOKEN:="00000000000000000000000000000000"} + +sudo -u postgres psql -d ${DB_NAME} << EOF +INSERT INTO ${DB_SCHEMA}."token" (token, description, date_issued) VALUES ('${ADMIN_TOKEN}', 'Admin token', NOW()); +EOF diff --git a/scripts/deploy_schema.py b/scripts/deploy_schema.py old mode 100644 new mode 100755 index ea7adf4..2d264ed --- a/scripts/deploy_schema.py +++ b/scripts/deploy_schema.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + # Replace with Alembic in due course import os