Skip to content

Instalacja

Montiu to aplikacja oparta o Django, uruchamiana w kontenerze Docker i publikowana przez Caddy pod subdomeną montiu.fulc.ro.

Aplikacja korzysta z istniejącego kontenera PostgreSQL.

Założenia konfiguracyjne:

  • Compose: /srv/docker/compose/montiu
  • Kod aplikacji: /srv/docker/projects/montiu
  • Dane aplikacji: /srv/docker/data/montiu
  • Sekrety: /srv/docker/secrets/montiu/app.env
  • Sieci Docker: proxy, internal
  • Panel Django Admin: /admin
  • Reverse proxy: Caddy
  • Serwer aplikacji: Gunicorn
  • Baza danych: PostgreSQL

Backup:

  • compose → Git
  • projects → Git
  • data → backup storage
  • secrets → backup storage
  • PostgreSQL → backup bazy danych

Dostęp do sieci Docker

Aplikacja wymaga dostępu do sieci proxy i internal.

Sieć proxy służy do komunikacji z Caddy.

Sieć internal służy do komunikacji z PostgreSQL.


Tworzenie katalogów

Tworzymy katalog Compose:

sudo mkdir -p /srv/docker/compose/montiu

Tworzymy katalog projektu:

sudo mkdir -p /srv/docker/projects/montiu

Tworzymy katalog danych aplikacji:

sudo mkdir -p /srv/docker/data/montiu/{media,static}

Tworzymy katalog sekretów:

sudo mkdir -p /srv/docker/secrets/montiu

Nadajemy właściciela:

sudo chown -R "$USER:$USER" /srv/docker/compose/montiu
sudo chown -R "$USER:$USER" /srv/docker/projects/montiu
sudo chown -R "$USER:$USER" /srv/docker/data/montiu
sudo chown -R "$USER:$USER" /srv/docker/secrets/montiu

Tworzenie projektu Django

Tworzymy projekt Django bez instalowania Pythona na hoście.

docker run --rm \
  -v /srv/docker/projects/montiu:/app \
  -w /app \
  python:3.13-slim-bookworm \
  sh -c "pip install 'Django>=5.2,<5.3' && django-admin startproject config ."

Poprawiamy właściciela plików utworzonych przez kontener:

sudo chown -R "$USER:$USER" /srv/docker/projects/montiu

Po wykonaniu polecenia struktura projektu wygląda mniej więcej tak:

/srv/docker/projects/montiu
├── config
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Tworzenie requirements.txt

Tworzymy plik zależności:

micro /srv/docker/projects/montiu/requirements.txt
Django>=5.2,<5.3
gunicorn
psycopg[binary]
whitenoise

gunicorn uruchamia aplikację Django w środowisku produkcyjnym.

psycopg odpowiada za komunikację z PostgreSQL.

whitenoise obsługuje statyczne pliki Django, dzięki czemu nie potrzebujemy Nginx ani osobnej konfiguracji statycznych plików w Caddy.


Tworzenie Dockerfile

Dockerfile przechowujemy razem z kodem projektu:

micro /srv/docker/projects/montiu/Dockerfile
FROM python:3.13-slim-bookworm

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt /tmp/requirements.txt

RUN pip install --no-cache-dir -r /tmp/requirements.txt

CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "2", "--access-logfile", "-", "--error-logfile", "-"]

Konfiguracja


Tworzenie pliku app.env

Plik zawiera konfigurację aplikacji oraz dane dostępowe do PostgreSQL.

micro /srv/docker/secrets/montiu/app.env
TZ=Europe/Warsaw

DJANGO_SECRET_KEY=TU_BARDZO_DLUGI_LOSOWY_KLUCZ
DJANGO_DEBUG=false

DJANGO_ALLOWED_HOSTS=montiu.fulc.ro
DJANGO_CSRF_TRUSTED_ORIGINS=https://montiu.fulc.ro

DB_HOST=postgres
DB_PORT=5432
DB_NAME=montiu
DB_USER=montiu
DB_PASSWORD=TU_SILNE_HASLO

DB_HOST musi odpowiadać nazwie kontenera lub aliasowi sieciowemu istniejącego PostgreSQL.

Przykładowo, jeżeli kontener nazywa się:

postgresql

ustawiamy:

DB_HOST=postgresql

Generowanie DJANGO_SECRET_KEY

Generujemy losowy sekret:

openssl rand -base64 48

Wklejamy wynik do:

DJANGO_SECRET_KEY=...

Konfiguracja Django

Edytujemy:

micro /srv/docker/projects/montiu/config/settings.py

Na początku pliku przed from pathlib import Path dodajemy:

import os

Konfiguracja sekretów

Zmieniamy:

SECRET_KEY = "..."

na:

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

Konfiguracja trybu produkcyjnego

Zmieniamy:

DEBUG = True

na:

DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"

Konfiguracja domeny

Ustawiamy:

ALLOWED_HOSTS = [
    host.strip()
    for host in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
    if host.strip()
]

Dodajemy:

CSRF_TRUSTED_ORIGINS = [
    origin.strip()
    for origin in os.environ.get(
        "DJANGO_CSRF_TRUSTED_ORIGINS",
        "",
    ).split(",")
    if origin.strip()
]

Konfiguracja HTTPS za Caddy

Za CSRF_TRUSTED_ORIGINS dodajemy:

SECURE_PROXY_SSL_HEADER = (
    "HTTP_X_FORWARDED_PROTO",
    "https",
)

Dzięki temu Django poprawnie rozpoznaje połączenie HTTPS przekazane przez Caddy.


Konfiguracja PostgreSQL

Odnajdujemy sekcję:

DATABASES = {
    ...
}

i zastępujemy ją:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ["DB_HOST"],
        "PORT": os.environ.get("DB_PORT", "5432"),
    }
}

Konfiguracja języka i strefy czasowej

Ustawiamy:

LANGUAGE_CODE = "pl"

oraz:

TIME_ZONE = "Europe/Warsaw"

Pozostawiamy:

USE_I18N = True
USE_TZ = True

Konfiguracja WhiteNoise

W sekcji:

MIDDLEWARE = [

bezpośrednio po:

"django.middleware.security.SecurityMiddleware",

dodajemy:

"whitenoise.middleware.WhiteNoiseMiddleware",

Czyli początek powinien wyglądać:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    ...
]

Konfiguracja plików statycznych

Ustawiamy:

STATIC_URL = "/static/"
STATIC_ROOT = "/data/static"

Dodajemy:

MEDIA_URL = "/media/"
MEDIA_ROOT = "/data/media"

Dodajemy konfigurację storage:

STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    },
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

Katalog:

/srv/docker/data/montiu/media

zawiera trwałe pliki przesłane przez aplikację i powinien być backupowany.

Katalog:

/srv/docker/data/montiu/static

zawiera wygenerowane pliki statyczne i może zostać odtworzony poleceniem collectstatic.


Konfiguracja Docker Compose

Przechodzimy do katalogu Compose:

cd /srv/docker/compose/montiu

Tworzymy:

micro compose.yaml
services:
  app:
    build:
      context: /srv/docker/projects/montiu
      dockerfile: Dockerfile

    container_name: montiu-app
    restart: unless-stopped

    env_file:
      - /srv/docker/secrets/montiu/app.env

    volumes:
      - /srv/docker/projects/montiu:/app
      - /srv/docker/data/montiu:/data

    networks:
      - proxy
      - internal

networks:
  proxy:
    external: true

  internal:
    external: true

Nie konfigurujemy:

ports:

ponieważ Caddy komunikuje się z aplikacją bezpośrednio przez sieć Docker proxy.


Budowanie obrazu

Przechodzimy do katalogu Compose:

cd /srv/docker/compose/montiu

Budujemy obraz:

docker compose build

Sprawdzenie połączenia z PostgreSQL

Możemy sprawdzić konfigurację Django:

docker compose run --rm app python manage.py check

Jeżeli połączenie z bazą oraz konfiguracja są poprawne, Django nie powinno zgłosić błędów.


Migracja bazy danych

Tworzymy podstawowe tabele Django:

docker compose run --rm app python manage.py migrate

Django utworzy między innymi tabele odpowiedzialne za:

  • użytkowników,
  • sesje,
  • uprawnienia,
  • grupy,
  • Django Admin.

Zbieranie plików statycznych

Uruchamiamy:

docker compose run --rm app python manage.py collectstatic --noinput

Pliki zostaną zapisane w:

/srv/docker/data/montiu/static

Tworzenie administratora

Tworzymy konto administratora:

docker compose run --rm app python manage.py createsuperuser

Podajemy:

Username
Email
Password

Konto umożliwia dostęp do:

/admin/

Uruchomienie aplikacji

Uruchamiamy kontener:

docker compose up -d

Sprawdzamy:

docker ps | grep montiu

Powinien działać kontener:

montiu-app

Sprawdzenie logów

docker logs -f montiu-app

Gunicorn powinien nasłuchiwać na:

0.0.0.0:8000

Konfiguracja reverse proxy

Dodajemy wpis do Caddyfile:

montiu.fulc.ro {
    reverse_proxy montiu-app:8000
}

Caddy i montiu-app muszą należeć do sieci:

proxy

Caddy automatycznie obsługuje HTTPS.

Przeładowujemy konfigurację:

docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile

Alternatywnie, jeżeli mamy skonfigurowany alias:

caddy-reload

Test działania

Sprawdzamy:

https://montiu.fulc.ro/admin/

Powinien pojawić się ekran logowania Django Admin.


Test konfiguracji produkcyjnej

Django posiada wbudowany mechanizm sprawdzania konfiguracji deploymentu.

Uruchamiamy:

docker compose exec app python manage.py check --deploy

Ewentualne ostrzeżenia analizujemy przed uruchomieniem aplikacji produkcyjnie.


Git


Repozytorium projektu

Kod aplikacji znajdujący się w:

/srv/docker/projects/montiu

przechowujemy w Git.

Przechodzimy do katalogu:

cd /srv/docker/projects/montiu

Tworzymy .gitignore:

micro .gitignore
__pycache__/
*.py[cod]
*.log

.venv/
venv/

.env
.env.*

.pytest_cache/
.mypy_cache/

staticfiles/
media/

.DS_Store
.idea/
.vscode/

Inicjalizujemy repozytorium:

git init
git add .
git commit -m "Initial Django project"

Repozytorium konfiguracji Compose

Katalog:

/srv/docker/compose/montiu

również może być przechowywany w Git.

Nie umieszczamy w nim haseł ani sekretów.


Dane trwałe

Backup storage powinien obejmować:

/srv/docker/data/montiu

oraz:

/srv/docker/secrets/montiu

Osobno wykonujemy backup PostgreSQL.

Najważniejsze dane aplikacji biznesowej będą znajdować się właśnie w PostgreSQL.


Podstawowe polecenia Django


Tworzenie nowej aplikacji Django

Przykładowo tworzymy główną aplikację Montiu:

docker compose exec app python manage.py startapp core

Możemy później tworzyć kolejne moduły, np.:

docker compose exec app python manage.py startapp sales

Tworzenie migracji

Po zmianach modeli:

docker compose exec app python manage.py makemigrations

Wykonywanie migracji

docker compose exec app python manage.py migrate

Django shell

docker compose exec app python manage.py shell

Sprawdzenie konfiguracji

docker compose exec app python manage.py check

Ponowne zebranie plików statycznych

docker compose exec app python manage.py collectstatic --noinput

Restart aplikacji

Po zmianach wymagających restartu:

docker compose restart app

Aktualizacja zależności

Po zmianie:

requirements.txt

przebudowujemy obraz:

docker compose up -d --build

Podgląd logów

Logi aplikacji:

docker logs -f montiu-app

lub:

cd /srv/docker/compose/montiu
docker compose logs -f app

Uwagi utrzymaniowe

  • Kod aplikacji znajduje się w projects i jest przechowywany w Git.
  • Compose znajduje się w compose i jest przechowywany w Git.
  • Sekrety nigdy nie trafiają do Git.
  • data jest przechowywane na backupowanym storage.
  • PostgreSQL jest backupowany niezależnie.
  • Po zmianach modeli wykonujemy makemigrations i migrate.
  • Po zmianach statycznych plików wykonujemy collectstatic.
  • Po zmianie requirements.txt przebudowujemy obraz.
  • Po zmianie app.env restartujemy kontener.
  • Do produkcji nie używamy python manage.py runserver.
  • Django działa przez Gunicorn.
  • Caddy odpowiada za HTTPS i reverse proxy.
  • Nie potrzebujemy Nginx.

Struktura wdrożenia

Internet
   │
   ▼
Caddy
   │
   │ sieć Docker: proxy
   ▼
montiu-app
Gunicorn
   │
   ▼
Django
   │
   │ sieć Docker: internal
   ▼
PostgreSQL

Warstwa aplikacyjna:

Django
Django Templates
HTMX
Alpine.js
Bootstrap
FullCalendar

HTMX, Alpine.js, Bootstrap i FullCalendar dołożymy podczas tworzenia właściwego interfejsu Montiu.

Nie są to osobne kontenery ani usługi.