diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..e9e91c4 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,49 @@ +name: build-image +on: + push: + branches: + - 'main' + +jobs: + ci: + uses: ./.github/workflows/ci.yaml + build-image: + name: build image + runs-on: ubuntu-latest + needs: [ci] # require tests to pass before build runs + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to the Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build backend image for amd64 + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + build-args: | + APP_ENV=production + push: true + tags: | + ghcr.io/${{ github.repository }}/wagtail-backend:latest + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Build nginx image for amd64 + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + build-args: | + APP_ENV=nginx-production + push: true + tags: | + ghcr.io/${{ github.repository }}/nginx-static:latest + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 19f92c0..1eb895c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,22 +2,26 @@ name: ci on: push: + branches: + - '*' + - '!main' + workflow_call: jobs: pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.12' - name: Run pre-commit tests - uses: pre-commit/action@v3.0.0 + uses: pre-commit/action@v3.0.1 unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install Dependencies @@ -30,14 +34,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set .env from .env.example - run: mv .env.example .env - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build image for amd64 and arm64 - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 13a8b5b..c12ad89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ +# Defining environment +ARG APP_ENV=dev + # Use an official Python runtime based on Debian 12 "bookworm" as a parent image. # Supported python3 versions: https://docs.wagtail.org/en/stable/releases/upgrading.html#compatible-django-python-versions -FROM python:3.12-slim-bookworm +FROM python:3.12-slim-bookworm AS base # Add user that will be used in the container. RUN useradd wagtail @@ -8,10 +11,7 @@ RUN useradd wagtail # Port used by this container to serve HTTP. EXPOSE 8000 -# Set environment variables. -# 1. Force Python stdout and stderr streams to be unbuffered. -# 2. Set PORT variable that is used by Gunicorn. This should match "EXPOSE" -# command. +# Set default environment variables. ENV PYTHONUNBUFFERED=1 \ PORT=8000 @@ -22,10 +22,11 @@ RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-r libjpeg62-turbo-dev \ zlib1g-dev \ libwebp-dev \ + netcat-openbsd \ && rm -rf /var/lib/apt/lists/* # Install the application server. -RUN pip install "gunicorn==20.0.4" +RUN pip install "gunicorn==23.0.0" # Install the project requirements. COPY requirements.txt / @@ -34,9 +35,7 @@ RUN pip install -r /requirements.txt # Use /app folder as a directory where the source code is stored. WORKDIR /app -# Set this directory to be owned by the "wagtail" user. This Wagtail project -# uses SQLite, the folder needs to be owned by the user that -# will be writing to the database file. +# Set this directory to be owned by the "wagtail" user. RUN chown wagtail:wagtail /app # Copy the source code of the project into the container. @@ -45,16 +44,24 @@ COPY --chown=wagtail:wagtail . . # Use user "wagtail" to run the build commands below and the server itself. USER wagtail -# Collect static files. +# Building the Production image +FROM base AS production-image +RUN echo "Building production image" +ENV DJANGO_SETTINGS_MODULE=tgno.settings.production +RUN python manage.py collectstatic --noinput --clear +ENTRYPOINT ["/app/entrypoint.prod.sh"] +CMD ["gunicorn", "tgno.wsgi:application"] + +# Building the nginx production image +FROM nginx:1.27-alpine AS nginx-production-image +RUN echo "Building nginx-production image" +COPY --from=production-image /app/static /usr/share/nginx/html/static + +# Building the Dev image +FROM base AS dev-image +RUN echo "Building dev image" +ENV DJANGO_SETTINGS_MODULE=tgno.settings.dev RUN python manage.py collectstatic --noinput --clear +CMD ["python", "manage.py", "0.0.0.0:8000"] -# Runtime command that executes when "docker run" is called, it does the -# following: -# 1. Migrate the database. -# 2. Start the application server. -# WARNING: -# Migrating database at the same time as starting the server IS NOT THE BEST -# PRACTICE. The database should be migrated manually or using the release -# phase facilities of your hosting platform. This is used only so the -# Wagtail instance can be started with a simple "docker run" command. -CMD set -xe; python manage.py migrate --noinput; gunicorn tgno.wsgi:application +FROM ${APP_ENV}-image \ No newline at end of file diff --git a/entrypoint.prod.sh b/entrypoint.prod.sh new file mode 100755 index 0000000..8e59be1 --- /dev/null +++ b/entrypoint.prod.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +while ! nc -z $POSTGRES_HOST $POSTGRES_PORT; do + sleep 0.5 + echo "Waiting for PostgreSQL ($POSTGRES_HOST:$POSTGRES_PORT)" +done + +python manage.py migrate --no-input +exec "$@" \ No newline at end of file diff --git a/tgno/settings/base.py b/tgno/settings/base.py index 37bb655..033e289 100644 --- a/tgno/settings/base.py +++ b/tgno/settings/base.py @@ -175,6 +175,6 @@ # Base URL to use when referring to full URLs within the Wagtail admin backend - # e.g. in notification emails. Don't include '/admin' or a trailing slash -WAGTAILADMIN_BASE_URL = "http://example.com" +WAGTAILADMIN_BASE_URL = env("WAGTAILADMIN_BASE_URL", "http://example.com") -WAGTAILAPI_BASE_URL = "http://example.com" +WAGTAILAPI_BASE_URL = env("WAGTAILAPI_BASE_URL", "http://example.com") diff --git a/tgno/settings/dev.py b/tgno/settings/dev.py index b318e8b..5f4c419 100644 --- a/tgno/settings/dev.py +++ b/tgno/settings/dev.py @@ -1,3 +1,5 @@ +from os import getenv as env + from .base import * # SECURITY WARNING: don't run with debug turned on in production! diff --git a/tgno/settings/production.py b/tgno/settings/production.py index 9ca4ed7..496f3b8 100644 --- a/tgno/settings/production.py +++ b/tgno/settings/production.py @@ -1,6 +1,28 @@ +from os import getenv as env + from .base import * DEBUG = False +SECRET_KEY = env("SECRET_KEY") +ALLOWED_HOSTS = env("ALLOWED_HOSTS", "localhost").split(",") +CSRF_TRUSTED_ORIGINS = env("CSRF_TRUSTED_ORIGINS", "http://localhost").split(",") +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + }, + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": env("DJANGO_LOG_LEVEL", "INFO"), + }, + }, +} try: from .local import *