Files
training-software/README.md
Paperclip CTO 3c2cbb6e39
All checks were successful
CI / lint (push) Successful in 13s
CI / test (push) Successful in 24s
CI / build-container (push) Successful in 6s
Apply FRONTEND_API_BASE_URL across compose profiles
2026-05-19 13:32:22 +02:00

5.6 KiB

Training Software

This repository contains a Django-based training platform backend plus a static customer portal frontend.

Features for customer environment setup

  • Per-customer organization profile with configurable license user limits (20, 50, 100, 1000)
  • Per-customer branding via company name and logo URL
  • Static frontend that can be hosted on Nginx and configured with the backend API domain at deploy time
  • CI pipeline for tests, linting, and container build

Production deployment with nginx

docker-compose.prod.yml starts the full stack in one command:

  • nginx — public-facing web server on port 80; serves the frontend SPA and Django static files directly; proxies all backend routes to gunicorn
  • web — Django/gunicorn (prod settings, 4 workers)
  • celery / celery-beat — background task workers
  • db — PostgreSQL 16
  • redis — Redis 7

1. Prerequisites

  • Docker Engine 24+ with Compose v2

2. Environment variables

cp .env.example .env

Edit .env and set at minimum:

Variable Notes
DJANGO_SECRET_KEY Long random string
DJANGO_ALLOWED_HOSTS Comma-separated hostnames, e.g. training.example.com
DB_PASSWORD PostgreSQL password
SECURE_SSL_REDIRECT Set false for HTTP-only; set true (or omit) when TLS is terminated at nginx or an upstream proxy

3. Start the stack

docker compose -f docker-compose.prod.yml up -d --build

4. Run migrations and create admin user

docker compose -f docker-compose.prod.yml exec web python manage.py migrate
docker compose -f docker-compose.prod.yml exec web python manage.py createsuperuser

5. Verify health

curl http://<host>/healthz/

HTTPS

To enable HTTPS, add ssl_certificate and ssl_certificate_key directives to nginx/nginx.conf and expose port 443 in docker-compose.prod.yml, or place a TLS-terminating reverse proxy (Traefik, Caddy, etc.) in front and set SECURE_SSL_REDIRECT=true.

Production deployment with dedicated frontend container

Use docker-compose.proxy.yml if you want your external nginx to be only a reverse proxy while this stack runs its own frontend container.

1. Start the stack

cp .env.example .env
docker compose -f docker-compose.proxy.yml up -d --build
docker compose -f docker-compose.proxy.yml exec web python manage.py migrate
docker compose -f docker-compose.proxy.yml exec web python manage.py createsuperuser

By default, the frontend container is exposed on http://localhost:8080. Set FRONTEND_PORT in .env.proxy if needed.

2. External nginx reverse-proxy example

server {
  listen 80;
  server_name training.example.com;

  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    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;
  }
}

Local development

1. Prerequisites

  • Python 3.12
  • PostgreSQL 16+
  • Redis 7+
  • Docker (recommended for containerised dev)

2. Environment variables

Create environment variables based on .env.example.

Required minimum values:

  • DJANGO_SECRET_KEY
  • DJANGO_ALLOWED_HOSTS
  • DATABASE_URL
  • REDIS_URL
  • DJANGO_SETTINGS_MODULE (use config.settings.local for dev)

3. Build and run with Docker Compose

docker compose up -d --build

4. Run migrations

docker compose exec web python manage.py migrate

5. Verify health

curl http://localhost:8000/healthz/

Local frontend in Docker (optional)

docker-compose.yml also includes a frontend service on http://localhost:8080.

Set FRONTEND_API_BASE_URL in .env to point the frontend to your backend API, for example:

FRONTEND_API_BASE_URL=http://localhost:8000

The same variable is supported by docker-compose.proxy.yml and docker-compose.prod.yml.

Organization profile API (license + branding)

Admin-only endpoint (requires admin role):

  • GET /api/v1/accounts/organizations/{org_id}/profile/
  • PATCH /api/v1/accounts/organizations/{org_id}/profile/

Fields

Field Type Description
company_name string Customer company name shown in the UI
license_user_limit integer Maximum number of active users — any positive integer (e.g. 20, 50, 100, 250, 1000)
brand_logo_url string (URL) URL of the customer's logo, displayed in the frontend header

Setting the license limit

license_user_limit accepts any positive integer — there are no fixed tiers. Set it to exactly the number of users your customer has licensed:

curl -X PATCH https://api.example.com/api/v1/accounts/organizations/<org_id>/profile/ \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "company_name": "Acme Corp",
    "license_user_limit": 250,
    "brand_logo_url": "https://cdn.example.com/acme-logo.svg"
  }'

Common values: 20 · 50 · 100 · 250 · 500 · 1000 — but any number works.

Frontend deployment on Nginx

The static frontend is under frontend/public.

1. Set backend API domain

Edit frontend/public/config.js and set:

window.APP_CONFIG = {
  API_BASE_URL: "https://api.example.com"
};

2. Serve via Nginx

Use frontend/nginx.conf as a reference server config and copy the frontend/public files to your Nginx web root.

CI for Gitea

Gitea Actions workflow file:

  • .gitea/workflows/ci.yml

It runs:

  • Ruff lint
  • Django test suite
  • Docker image build