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→ Gitprojects→ Gitdata→ backup storagesecrets→ 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
projectsi jest przechowywany w Git. - Compose znajduje się w
composei jest przechowywany w Git. - Sekrety nigdy nie trafiają do Git.
datajest przechowywane na backupowanym storage.- PostgreSQL jest backupowany niezależnie.
- Po zmianach modeli wykonujemy
makemigrationsimigrate. - Po zmianach statycznych plików wykonujemy
collectstatic. - Po zmianie
requirements.txtprzebudowujemy obraz. - Po zmianie
app.envrestartujemy 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.