Skip to content

Commit

Permalink
feat: server docker build
Browse files Browse the repository at this point in the history
  • Loading branch information
tymees committed Oct 30, 2024
1 parent b9a2c6e commit 1f88f44
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 18 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/build-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Build and Push Docker Images

on:
push:
tags:
- '*'
workflow_dispatch:

env:
IMAGE_NAME: humitifier-server
DOCKERFILE_PATH: ./humitifier-server/Dockerfile

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push main image
uses: docker/build-push-action@v4
with:
context: .
file: ${{ env.DOCKERFILE_PATH }}
push: true
tags: |
# ghcr.io/centrefordigitalhumanities/humitifier/${{ env.IMAGE_NAME }}:latest
ghcr.io/centrefordigitalhumanities/humitifier/${{ env.IMAGE_NAME }}:${{ github.ref_name }}

- name: Grype Scan
id: scan
uses: anchore/scan-action@v3
with:
image: ghcr.io/centrefordigitalhumanities/humitifier/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
fail-build: false

- name: upload Grype SARIF report
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
27 changes: 27 additions & 0 deletions humitifier-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM python:3.13.0-alpine3.20 AS builder

ENV PYTHONUNBUFFERED=1

RUN pip install poetry && poetry config virtualenvs.in-project true
RUN apk add postgresql-dev gcc musl-dev libffi-dev

WORKDIR /app

COPY pyproject.toml poetry.lock ./
COPY src/ ./src

RUN poetry install --without dev --with=production -vvv

FROM python:3.13.0-alpine3.20

WORKDIR /app

RUN apk add postgresql-libs

COPY --from=builder /app .
COPY docker/gunicorn.conf.py ./
COPY docker/entrypoint.sh ./

RUN .venv/bin/python src/manage.py collectstatic --noinput

CMD ["sh", "entrypoint.sh"]
10 changes: 10 additions & 0 deletions humitifier-server/docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

# Activate our venv
source /app/.venv/bin/activate

# Migrate the database
python src/manage.py migrate

# Run da server
gunicorn humitifier_server.wsgi:application -c gunicorn.conf.py "$@"
7 changes: 7 additions & 0 deletions humitifier-server/docker/gunicorn.conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os

bind = "0.0.0.0:8000"
workers = 3
capture_output = True
# How verbose the Gunicorn error logs should be
loglevel = os.getenv("LOG_LEVEL", "WARNING")
45 changes: 40 additions & 5 deletions humitifier-server/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion humitifier-server/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ django-filter = "^24.3"
markdown = "^3.7"
django-debug-toolbar = "^4.4.6"
django-simple-menu = "^2.1.3"
whitenoise = "^6.8.2"


[tool.poetry.group.dev.dependencies]
black = "^24.10.0"
psycopg2-binary = {version = "^2.9.10", optional = true}
faker = "^30.8.0"

[tool.poetry.group.production]
optional = true

[tool.poetry.group.production.dependencies]
psycopg2 = {version = "^2.9.10", optional = true}
psycopg2 = "^2.9.10"
gunicorn = "^23.0.0"

[build-system]
requires = ["poetry-core"]
Expand Down
6 changes: 6 additions & 0 deletions humitifier-server/src/humitifier_server/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from os import environ

get = environ.get

def get_boolean(key: str, default) -> bool:
return get(key, default=str(default)).lower() in ("true", "1", "yes", 't')
86 changes: 74 additions & 12 deletions humitifier-server/src/humitifier_server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

from pathlib import Path
from . import env

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand All @@ -20,17 +21,20 @@
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-ffdjavjsp%s%b069$aai#h7odtbd#!q8uu7=hn1tv&y$gdq17_"
SECRET_KEY = env.get(
"DJANGO_SECRET_KEY",
"django-insecure-ffdjavjsp%s%b069$aai#h7odtbd#!q8uu7=hn1tv&y$gdq17_"
)

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = env.get_boolean("DJANGO_DEBUG", False)

ALLOWED_HOSTS = []
INTERNAL_IPS = [
# ...
"127.0.0.1",
# ...
]
INTERNAL_IPS = ["127.0.0.1",]

_env_hosts = env.get("DJANGO_ALLOWED_HOSTS", default=None)
if _env_hosts:
ALLOWED_HOSTS += _env_hosts.split(",")


# Application definition
Expand Down Expand Up @@ -58,6 +62,7 @@

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
Expand Down Expand Up @@ -103,15 +108,62 @@
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "postgres",
"USER": "postgres",
"PASSWORD": "postgres",
"HOST": "127.0.0.1",
"PORT": "6432",
"NAME": env.get("POSTGRES_DB", default="postgres"),
"USER": env.get("POSTGRES_USER", default="postgres"),
"PASSWORD": env.get("POSTGRES_PASSWORD", default="postgres"),
"HOST": env.get("POSTGRES_HOST", default="127.0.0.1"),
"PORT": env.get("POSTGRES_PORT", default="5432"),
}
}


# Security

_https_enabled = env.get_boolean("DJANGO_HTTPS", default=False)

X_FRAME_OPTIONS = "DENY"
SECURE_SSL_REDIRECT = _https_enabled

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

SESSION_COOKIE_SECURE = _https_enabled
CSRF_COOKIE_SECURE = _https_enabled
# Needed to work in kubernetes, as the app may be behind a proxy/may not know it's
# own domain
SESSION_COOKIE_DOMAIN = env.get("SESSION_COOKIE_DOMAIN", default=None)
CSRF_COOKIE_DOMAIN = env.get("CSRF_COOKIE_DOMAIN", default=None)
SESSION_COOKIE_NAME = env.get("SESSION_COOKIE_NAME",
default="humitifier_sessionid")
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_AGE = 60 * 60 * 12 # 12 hours

CSRF_TRUSTED_ORIGINS = [
f"http{'s' if _https_enabled else ''}://{host}" for host in ALLOWED_HOSTS
]

# Logging

LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"root": {
"handlers": ["console"],
"level": env.get("LOG_LEVEL", default="INFO"),
},
"loggers": {
"django": {
"handlers": ["console"],
"level": env.get("DJANGO_LOG_LEVEL", default="INFO"),
"propagate": False,
},
},
}

# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

Expand Down Expand Up @@ -152,3 +204,13 @@
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

# Whitenoise

STATIC_ROOT = BASE_DIR / "static"

STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}

0 comments on commit 1f88f44

Please sign in to comment.