Small refactor of scripts, Dockerfile, and docker-compose to support load balancing, and mutiple replicas. Various fixes related to playwright installation in container environment, static file handling, and etc.

This commit is contained in:
badblocks 2025-05-06 23:14:36 -07:00
parent 9b3b3d099f
commit 2dba19a77e
12 changed files with 109 additions and 127 deletions

View file

@ -1,25 +1,16 @@
# syntax=docker/dockerfile:1.9 # syntax=docker/dockerfile:1.9
FROM python3.13-slim-bookworm AS build ### Start build prep.
### This should be a separate build container for better reuse.
FROM python:3.13-slim-bookworm AS build
# The following does not work in Podman unless you build in Docker # The following does not work in Podman unless you build in Docker
# compatibility mode: <https://github.com/containers/podman/issues/8477> # compatibility mode: <https://github.com/containers/podman/issues/8477>
# You can manually prepend every RUN script with `set -ex` too. # You can manually prepend every RUN script with `set -ex` too.
SHELL ["sh", "-exc"] SHELL ["sh", "-exc"]
# Ensure apt-get doesn't open a menu on you. # Ensure apt-get doesn't open a menu
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
### Start build prep.
### This should be a separate build container for better reuse.
# RUN <<EOT
# apt-get update -qy
# apt-get install -qyy \
# -o APT::Install-Recommends=false \
# -o APT::Install-Suggests=false \
# nodejs npm
# EOT
COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /usr/local/bin/uv COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /usr/local/bin/uv
# - Silence uv complaining about not being able to use hard links, # - Silence uv complaining about not being able to use hard links,
@ -32,8 +23,6 @@ ENV UV_LINK_MODE=copy \
UV_PYTHON=python3.13 \ UV_PYTHON=python3.13 \
UV_PROJECT_ENVIRONMENT=/app UV_PROJECT_ENVIRONMENT=/app
### End build prep -- this is where your app Dockerfile should start.
# Synchronize DEPENDENCIES without the application itself. # Synchronize DEPENDENCIES without the application itself.
# This layer is cached until uv.lock or pyproject.toml change, which are # This layer is cached until uv.lock or pyproject.toml change, which are
# only temporarily mounted into the build container since we don't need # only temporarily mounted into the build container since we don't need
@ -60,7 +49,7 @@ RUN --mount=type=cache,target=/root/.cache \
# --no-dev \ # --no-dev \
# --no-editable # --no-editable
### End build prep
########################################################################## ##########################################################################
FROM python:3.13-slim-bookworm FROM python:3.13-slim-bookworm
@ -68,64 +57,58 @@ SHELL ["sh", "-exc"]
ARG ENV_FILE=.env.production ARG ENV_FILE=.env.production
# Optional: add the application virtualenv to search path.
ENV PATH=/app/bin:$PATH ENV PATH=/app/bin:$PATH
ENV PYTHONPATH=/code ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED=1
ENV HOME /code ENV HOME=/code
ENV DJANGO_SETTINGS_MODULE=django_project.settings
# Don't run your app as root. WORKDIR /app
# Don't run app as root
RUN <<EOT RUN <<EOT
groupadd -r app groupadd -r app -g 10003
useradd -r -d /app -g app -N app useradd -r -d /app -u 10003 -s /sbin/nologin -g app -N app
EOT EOT
# Note how the runtime dependencies differ from build-time ones. # Runtime dependencies
# Notably, there is no uv either!
RUN <<EOT RUN <<EOT
apt-get update -qy apt-get update -qy
apt-get install -qyy \ apt-get install -qyy \
-o APT::Install-Recommends=false \ -o APT::Install-Recommends=false \
-o APT::Install-Suggests=false \ -o APT::Install-Suggests=false \
nodejs xvfb curl xvfb curl
RUN playwright install-deps && playwright install
apt-get clean apt-get clean
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
EOT EOT
# COPY docker-entrypoint.sh /
# ENTRYPOINT ["/docker-entrypoint.sh"]
# See <https://hynek.me/articles/docker-signals/>. # See <https://hynek.me/articles/docker-signals/>.
STOPSIGNAL SIGINT STOPSIGNAL SIGINT
# Copy the pre-built `/app` directory to the runtime container
# and change the ownership to user app and group app in one step.
COPY --from=build --chown=app:app /app /app COPY --from=build --chown=app:app /app /app
RUN playwright install --with-deps
# If your application is NOT a proper Python package that got # If your application is NOT a proper Python package that got
# pip-installed above, you need to copy your application into # pip-installed above, you need to copy your application into
# the container HERE: # the container HERE and set the WORKDIR:
COPY . /code COPY --chown=app:app ./ /code
WORKDIR /code
USER app ENV PYTHONPATH=/code
#WORKDIR /app # If python-packaged # WORKDIR /app # if python-packaged
WORKDIR /code # If not python-packaged
# Strictly optional, but I like it for introspection of what I've built
# and run a smoke test that the application can, in fact, be imported.
# RUN <<EOT
# python -V
# python -Im site
# python -Ic 'import the_app'
# EOT`
RUN rm -f .env RUN rm -f .env
COPY ${ENV_FILE} .env COPY ${ENV_FILE} .env
COPY --chown=app:app --chmod=700 /scripts/entrypoint.sh /entrypoint.sh
COPY --chown=app:app --chmod=700 /scripts/deploy.sh /deploy.sh
ENTRYPOINT ["/entrypoint.sh"]
USER app
EXPOSE 8000 EXPOSE 8000
HEALTHCHECK CMD curl --fail http://localhost:8000/health/ || exit 1 HEALTHCHECK CMD curl --fail http://localhost:8000/health/ || exit 1
CMD ["granian", "--interface", "wsgi", "django_project.wsgi", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] CMD ["granian", "--interface", "wsgi", "django_project.wsgi", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--respawn-failed-workers", "--workers-kill-timeout", "60"]

View file

@ -1,5 +0,0 @@
#!/bin/bash
uv run manage.py makemigrations --check && uv run manage.py migrate --noinput
uv run manage.py clear_cache
uv run manage.py collectstatic -c --no-input
NODE_ENV=production uv run node ./theme/static_src/node_modules/@tailwindcss/cli/dist/index.mjs -m -i ./theme/static_src/src/styles.css -o ./theme/static/css/dist/styles.css

View file

@ -1,4 +1,13 @@
services: services:
loba:
image: lucaslorentz/caddy-docker-proxy:2.9
ports:
- 8000:8000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- caddy_data:/data
depends_on:
- web
web: web:
build: build:
context: . context: .
@ -7,8 +16,9 @@ services:
ENV_FILE: .env.dev ENV_FILE: .env.dev
volumes: volumes:
- .:/code:z - .:/code:z
ports: labels:
- 8000:8000 caddy: ":8000"
caddy.reverse_proxy: "{{upstreams 8000}}"
depends_on: depends_on:
- db - db
db: db:
@ -23,4 +33,5 @@ services:
volumes: volumes:
postgres_data: postgres_data:
labels: labels:
- "db_is_resettable_via_script" - "db_is_resettable_via_script"
caddy_data:

View file

@ -1,15 +1,25 @@
services: services:
loba:
image: lucaslorentz/caddy-docker-proxy:2.9
ports:
- 8000:8000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- caddy_data:/data
depends_on:
- web
web: web:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
ENV_FILE: .env.production ENV_FILE: .env.dev
volumes: deploy:
- .:/code:z mode: replicated
ports: replicas: 4
- 8000:8000
volumes:
postgres_data:
labels: labels:
- "db_is_resettable_via_script" caddy: ":8000"
caddy.reverse_proxy: "{{upstreams 8000}}"
volumes:
caddy_data:

21
package-lock.json generated
View file

@ -1,21 +0,0 @@
{
"name": "code",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"daisyui": "^5.0.0-beta.9"
}
},
"node_modules/daisyui": {
"version": "5.0.0-beta.9",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.0-beta.9.tgz",
"integrity": "sha512-V+To8o1O8AaxSgdk9QrjXyq/e1AhdW1Z6oUI5iwrOjPs8avM7VQNqoTDCAE5rM0NcMbUfmFgQH8h8guiQ5QPOA==",
"dev": true,
"funding": {
"url": "https://github.com/saadeghi/daisyui?sponsor=1"
}
}
}
}

View file

@ -1,5 +0,0 @@
{
"devDependencies": {
"daisyui": "^5.0.0-beta.9"
}
}

5
scripts/deploy.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/bash
django-admin makemigrations --noinput --check && django-admin migrate --noinput
django-admin clear_cache
django-admin collectstatic -c --no-input

View file

@ -1,23 +1,21 @@
#!/bin/bash #!/bin/bash
# Make the script exit when a command fails. set -exc
set -e
# Define a cleanup function to handle CTRL-C (SIGINT) # check if the startup command has been provided
cleanup() { if [ "$1" == "" ]; then
echo "CTRL-C caught! Shutting down Docker Compose services..." echo "Startup command not set. Exiting"
docker compose -f docker-compose_db_only.yml down exit;
exit 1 fi
}
# Set trap to call cleanup() when SIGINT (Ctrl-C) is received. # check if the $DJANGO_SETTINGS_MODULE environment variable has been set
trap cleanup SIGINT if [ "$DJANGO_SETTINGS_MODULE" == "" ]; then
echo "Environment variable 'DJANGO_SETTINGS_MODULE' not set. Exiting."
exit;
else
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
fi
# Restart compose services. /deploy.sh
echo "Restarting compose services..."
docker compose -f docker-compose_db_only.yml down
docker compose -f docker-compose_db_only.yml up -d
cd theme/static_src echo "Enviroment is correct and deploy.sh has been run - executing command: '$@'"
uv run npm run dev & exec "$@" && exit 0
cd ../../
uv run python manage.py runserver

12
scripts/prebuild.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash
# Remove all files in staticfiles except .gitkeep
if [ -d "staticfiles" ]; then
find staticfiles -type f ! -name '.gitkeep' -delete
find staticfiles -type d -empty -delete
fi
# Build the tailwind theme css
cd theme/static_src
npm install . && npm run build
cd ../../

9
scripts/rebuild-and-run.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
docker compose down
# run prebuild tasks
./scripts/prebuild.sh
# Build the docker image
docker compose build && docker compose up -d

View file

@ -4,31 +4,16 @@ set -e
# Reset the database and migrations. # Reset the database and migrations.
echo "Resetting database and migrations... " echo "Resetting database and migrations... "
docker compose -f docker-compose_db_only.yml down \ docker compose down \
&& docker compose -f docker-compose_entire_app.yml down \
&& docker volume prune -a --filter label=db_is_resettable_via_script \ && docker volume prune -a --filter label=db_is_resettable_via_script \
&& find . -path "*/migrations/00*.py" -delete \ && find . -path "*/migrations/00*.py" -delete \
&& docker compose -f docker-compose_db_only.yml up -d && docker compose up -d
# Wait for the database to be ready. # Wait for the database to be ready.
echo "Waiting for the database to be ready..." echo "Waiting for the database to be ready..."
sleep 10 sleep 10
echo "Resetting static files..."
uv run python manage.py collectstatic -c --no-input
echo "Running makemigrations..."
uv run python manage.py makemigrations
echo "Running migrations..."
uv run python manage.py migrate
echo "Loading seed data..." echo "Loading seed data..."
uv run python manage.py loaddata seed/0* docker compose exec -it web env DJANGO_SETTINGS_MODULE=django_project.settings django-admin loaddata /code/seed/0*
echo "Running deploy script..." echo "Done & Started!"
./deploy.sh
docker compose -f docker-compose_db_only.yml down
echo "Done!"

0
staticfiles/.gitkeep Normal file
View file