Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker Deployment for Kelvin core #504

Merged
merged 12 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
tasks/
submits/
submit_results/
frontend/
.venv/
29 changes: 21 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
# PostgreSQL connection information
DB_HOST=127.0.0.1
DB_DATABASE=kelvin
DB_USERNAME=kelvin
DB_PASSWORD=kelvin

# Path on the local disk where the data from a PostgreSQL instance running
# inside Docker will be stored
KELVIN_DB_PATH=./kelvin-data/db
DATABASE__HOSTNAME=127.0.0.1
DATABASE__PORT=5432
DATABASE__DB=kelvin
DATABASE__USERNAME=kelvin
DATABASE__PASSWORD=kelvin

# Redis connection information
REDIS__HOSTNAME=127.0.0.1
REDIS__PORT=6379

# Data storage paths
DATABASE__DATA_PATH=./kelvin_data/data
REDIS__DATA_PATH=./kelvin_data/redis

# Logs paths
NGINX__LOGS_PATH=./kelvin_data/logs/nginx

# Configuration paths
NGINX__CERTS_PATH=./certs
NGINX__CONFIG_PATH=./nginx.conf
KELVIN__LOCAL_SETTINGS_PATH=./local_settings.py
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,7 @@ web/static/dolos

survey/surveys/*.yaml
exams/

static/

kelvin_data/
28 changes: 28 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-json
- id: check-toml
- id: check-shebang-scripts-are-executable
- id: end-of-file-fixer
name: Makes sure files end in a newline and only a newline.
entry: end-of-file-fixer
types: [text]
- id: trailing-whitespace
name: Trims trailing whitespace.
entry: trailing-whitespace-fixer
types: [text]
- id: mixed-line-ending
args: [ --fix=lf ]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.7
hooks:
- id: ruff-format
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.7
hooks:
- id: ruff
args: [ --fix ]
77 changes: 61 additions & 16 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,67 @@
FROM python:3.12
FROM ghcr.io/astral-sh/uv:python3.12-bookworm AS build-backend

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y \
-o APT::Install-Recommends=false \
-o APT::Install-Suggests=false \
libsasl2-dev \
libgraphviz-dev \
graphviz

RUN apt-get update && apt-get install -y libsasl2-dev libgraphviz-dev graphviz
WORKDIR /app

COPY requirements.txt /kelvin/requirements.txt
RUN pip install -r /kelvin/requirements.txt
COPY pyproject.toml uv.lock ./

RUN apt-get update \
&& apt-get install -y ca-certificates curl gnupg lsb-release \
&& curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y docker-ce docker-ce-cli containerd.io
RUN uv sync --frozen --no-dev --no-install-project --compile-bytecode

WORKDIR /kelvin
FROM node:22.9.0-bookworm-slim AS build-frontend

#RUN addgroup --gid 1000 user
#RUN adduser --disabled-password --gecos '' --uid $HOST_USER_ID --gid 1000 user
#USER user
WORKDIR /frontend

COPY frontend .

RUN mkdir -p /web/static

RUN npm run build
JersyJ marked this conversation as resolved.
Show resolved Hide resolved

FROM python:3.12-bookworm AS runtime

RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y \
-o APT::Install-Recommends=false \
-o APT::Install-Suggests=false \
graphviz && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

WORKDIR /app

# Create new user to run app process as unprivilaged user
RUN groupadd --gid 1001 uvicorn && \
useradd --uid 1001 --gid 1001 --shell /bin/false --system uvicorn

RUN chown -R uvicorn:uvicorn /app

COPY --from=build-backend --chown=uvicorn:uvicorn /app .
ENV PATH="/app/.venv/bin:$PATH"

COPY --chown=uvicorn:uvicorn web ./web
COPY --from=build-frontend --chown=uvicorn:uvicorn /web/static/frontend.[js|css]|dolos ./web/static/
JersyJ marked this conversation as resolved.
Show resolved Hide resolved

COPY --chown=uvicorn:uvicorn kelvin ./kelvin
COPY --chown=uvicorn:uvicorn templates ./templates
COPY --chown=uvicorn:uvicorn evaluator ./evaluator
COPY --chown=uvicorn:uvicorn survey ./survey
COPY --chown=uvicorn:uvicorn common ./common
COPY --chown=uvicorn:uvicorn api ./api
COPY --chown=uvicorn:uvicorn manage.py .

USER uvicorn

RUN python manage.py collectstatic --no-input

EXPOSE 8000

CMD ["uvicorn", "kelvin.asgi:application", "--host", "0.0.0.0", "--port", "8000"]
78 changes: 66 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,72 @@
version: "3.9"
services:
db:
image: postgres:14
ports:
- "5432:5432"
app:
container_name: kelvin_app
depends_on:
- db
- redis
profiles: [prod]
build:
context: .
dockerfile: Dockerfile
target: runtime
environment:
- DATABASE__HOSTNAME=db
- DATABASE__PORT=5432
- DATABASE__DB=${DATABASE__DB}
- DATABASE__USERNAME=${DATABASE__USERNAME}
- DATABASE__PASSWORD=${DATABASE__PASSWORD}
- REDIS__HOST=redis
- REDIS__PORT=6379
env_file:
- .env
volumes:
- ${KELVIN_DB_PATH?Absolute path to Kelvin database storage}:/var/lib/postgresql/data
- kelvin_app_static:/app/static
- ${KELVIN__LOCAL_SETTINGS_PATH}:/app/local_settings.py:ro
restart: unless-stopped
expose:
- "8000"

db:
container_name: kelvin_db
image: postgres:14
environment:
- POSTGRES_DB=${DB_DATABASE?Name of PostgreSQL database}
- POSTGRES_USER=${DB_USERNAME?PostgreSQL username}
- POSTGRES_PASSWORD=${DB_PASSWORD?PostgreSQL password}
redis:
image: redis
- POSTGRES_DB=${DATABASE__DB}
- POSTGRES_USER=${DATABASE__USERNAME}
- POSTGRES_PASSWORD=${DATABASE__PASSWORD}
env_file:
- .env
volumes:
- ${DATABASE__DATA_PATH}:/var/lib/postgresql/data
restart: unless-stopped
ports:
- "6379:6379"
- "${DATABASE__HOSTNAME}:${DATABASE__PORT}:5432"

redis:
container_name: kelvin_redis
image: redis:latest
restart: unless-stopped
env_file:
- .env
volumes:
- ${REDIS__DATA_PATH}:/data
ports:
- "${REDIS__HOSTNAME}:${REDIS__PORT}:6379"
command: "redis-server --save 60 1 --loglevel warning"

nginx:
image: nginx:stable
container_name: kelvin_nginx
depends_on:
- app
profiles: [prod]
ports:
- "80:80"
- "443:443"
volumes:
- ${NGINX__CONFIG_PATH}:/etc/nginx/nginx.conf:ro
- ${NGINX__CERTS_PATH}:/etc/nginx/certs:ro
- ${NGINX__LOGS_PATH}:/var/log/nginx
- kelvin_app_static:/app/static

volumes:
kelvin_app_static:
6 changes: 6 additions & 0 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ Do not forget to activate the virtual environment if you want to work with Pytho
$ source .venv/bin/activate
```

## Git pre-commit hooks
To ensure that the code is formatted correctly and linted, you can install pre-commit hooks:
```bash
$ pre-commit install
```

## Building the frontend
For building the frontend, you'll need `npm`:
```bash
Expand Down
16 changes: 16 additions & 0 deletions kelvin/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for kelvin project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "kelvin.settings")

application = get_asgi_application()
28 changes: 18 additions & 10 deletions kelvin/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ["127.0.0.1", "localhost", "web"]
ALLOWED_HOSTS = ["127.0.0.1", "localhost", "app", "kelvin.cs.vsb.cz"]

CSRF_TRUSTED_ORIGINS = [
"https://127.0.0.1",
"https://localhost",
"https://app",
"https://kelvin.cs.vsb.cz",
]

# Application definition

Expand Down Expand Up @@ -105,10 +112,11 @@
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.getenv("DB_HOST", "127.0.0.1"),
"USER": os.getenv("DB_USERNAME"),
"PASSWORD": os.getenv("DB_PASSWORD"),
"NAME": os.getenv("DB_DATABASE"),
"HOST": os.getenv("DATABASE__HOSTNAME", "127.0.0.1"),
"PORT": os.getenv("DATABASE__PORT", 5432),
"NAME": os.getenv("DATABASE__DB"),
"USER": os.getenv("DATABASE__USERNAME"),
"PASSWORD": os.getenv("DATABASE__PASSWORD"),
}
}

Expand Down Expand Up @@ -175,14 +183,14 @@
CAS_CREATE_USER = False
CAS_FORCE_CHANGE_USERNAME_CASE = "upper"

redis_host = os.getenv("REDIS_HOST", "127.0.0.1")
redis_port = os.getenv("REDIS_EXPOSE_PORT", 6379)
redis_connection = f"redis://{redis_host}:{redis_port}/0"
REDIS_HOST = os.getenv("REDIS__HOST", "127.0.0.1")
REDIS_PORT = os.getenv("REDIS__PORT", 6379)
REDIS_CONNECTION = f"redis://{REDIS_HOST}:{REDIS_PORT}/0"

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": redis_connection,
"LOCATION": REDIS_CONNECTION,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
Expand All @@ -204,7 +212,7 @@
}

# For django-tasks-scheduler
SCHEDULER_QUEUES = {"default": {"HOST": redis_host, "PORT": redis_port}}
SCHEDULER_QUEUES = {"default": {"HOST": REDIS_HOST, "PORT": REDIS_PORT}}

LOGGING = {
"version": 1,
Expand Down
51 changes: 51 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
events {
JersyJ marked this conversation as resolved.
Show resolved Hide resolved
worker_connections 1024;
}

http {

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}


upstream django {
server kelvin_app:8000; # Uvicorn running Django app
}

server {
listen 443 ssl;
server_name yourdomain.com;

ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;

location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_buffering off;
}

# Serve static files
location /static/ {
autoindex on;
alias /app/static/;
include /etc/nginx/mime.types;
}

}

# Redirect all HTTP requests to HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
}
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ dependencies = [
"typing-extensions==4.11.0",
"Unidecode==1.3.6",
"uWSGI==2.0.24",
"uvicorn[standard]>=0.30.6",
]

[tool.uv]
dev-dependencies = [
"ruff==0.6.3"
"pre-commit>=3.8.0",
"ruff==0.6.3",
]
Loading