Lompat ke konten
dokumentasi.id
Commit 32b2830
v1
Star0

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

3

Arsitektur Sistem

Hydro berjalan sebagai tiga container: web melayani berkas statis sekaligus meneruskan /api/, api menjalankan Django lewat Gunicorn, dan db menyimpan seluruh data di PostgreSQL 18.1. Gambaran itu sudah terlihat di compose.yaml:3-46. Produksi memakai pembagian container yang sama, tetapi menambahkan jaringan internal dan pembatasan filesystem di compose.prod.yaml:1-21 serta compose.prod.yaml:22-40.

Topologi container

Browser hanya berhubungan dengan container web. Di dalamnya tidak ada Node.js: image akhir berisi Nginx dan isi /src/out hasil npm run build (deploy/web.Dockerfile:1-14). Permintaan ke /api/ diteruskan Nginx ke http://api:8000 di dalam jaringan Compose (deploy/nginx.conf:21-33). Container api menjalankan gunicorn config.wsgi dan menyimpan unggahan di volume media (compose.yaml:16-33), sedangkan db memakai postgres:18.1 dengan healthcheck pg_isready -U hydro -d hydro (compose.yaml:35-46).

graph TD
  B["Browser"] -->|"443/TLS"| W
  W["web: nginx-unprivileged 8443"]
  W -->|"location /api/ proxy_pass"| A
  W --> S["berkas statis /src/out"]
  A["api: gunicorn config.wsgi 8000"] -->|"psycopg"| D
  A --- M[("media:/data/media")]
  D["db: postgres:18.1 5432"]
  D --- P[("pgdata:/var/lib/postgresql")]
  style W fill:#f59e0b,color:#000
  style A fill:#2d7d46,color:#fff
  style S fill:#8b5cf6,color:#fff
  style D fill:#336791,color:#fff
  style M fill:#336791,color:#fff
  style P fill:#336791,color:#fff
%% fig: Topologi container Hydro
%% desc: Browser masuk ke container web, Nginx menyajikan berkas statis dan meneruskan /api/ ke Gunicorn, yang membaca PostgreSQL serta volume media.
Fig. 1: Topologi container HydroBrowser masuk ke container web, Nginx menyajikan berkas statis dan meneruskan /api/ ke Gunicorn, yang membaca PostgreSQL serta volume media.

Perbedaan kedua berkas compose terletak pada cara container dijalankan, bukan pada pembagian tugasnya.

Aspekcompose.yaml (lokal)compose.prod.yaml (produksi)
Sumber imagebuild dari deploy/web.Dockerfile dan ./backendghcr.io/${GHCR_OWNER:?}/hydro-web:${APP_VERSION:?} dan hydro-api
Port web127.0.0.1:8080:8080${TAILSCALE_IP:?}:443:8443
Jaringanjaringan default Composeedge, app, data; app dan data memakai internal: true
Restarttidak disetrestart: unless-stopped
Filesystemdapat ditulisread_only: true dengan tmpfs: /tmp
Urutan startdepends_on: condition: service_healthysama, sekali lagi service_healthy

Di komputer sendiri, container web memakai deploy/nginx.lokal.conf yang dipasang sebagai volume read-only, tanpa TLS dan tanpa location /_next/static/ khusus (compose.yaml:3-14, deploy/nginx.lokal.conf:1-17). Jaringan produksi memisahkan edge untuk trafik masuk, app untuk Nginx ke Gunicorn, dan data untuk Gunicorn ke PostgreSQL; app serta data sama-sama internal: true sehingga hanya bisa diakses dari dalam (compose.prod.yaml:58-63).

Perjalanan satu request

Muat halaman dan panggilan data menempuh jalur berbeda. Nginx menyajikan index.html dari root /usr/share/nginx/html dengan Cache-Control "no-cache" dan try_files $uri $uri/index.html /404.html (deploy/nginx.conf:35-44). Setelah halaman hidup, kode frontend memanggil fetch dengan credentials: "same-origin" dan menyertakan X-CSRFToken dari cookie csrftoken pada metode selain GET (frontend/src/lib/api.ts:31-41). Karena origin-nya sama, panggilan itu mendarat di location /api/ milik Nginx.

sequenceDiagram
  participant B as Browser
  participant N as web Nginx
  participant G as api Gunicorn
  participant J as Django + DRF
  participant P as db PostgreSQL
  B->>N: GET /produk/
  N-->>B: index.html dengan Cache-Control no-cache
  B->>N: fetch /api/produk/ credentials same-origin
  N->>G: proxy_pass http://api:8000
  G->>J: config.wsgi
  J->>J: MIDDLEWARE Security, Session, CSRF, Auth
  J->>P: query ORM
  P-->>J: rows
  J-->>G: JSONRenderer
  G-->>N: 200 application/json
  N-->>B: header keamanan dari keamanan.conf
  Note over B,N: metode tulis mengirim X-CSRFToken dari cookie csrftoken
%% fig: Siklus satu halaman dan satu panggilan API
%% desc: Nginx menyajikan halaman statis, lalu meneruskan panggilan /api/ ke Gunicorn, middleware Django, dan PostgreSQL.
Fig. 2: Siklus satu halaman dan satu panggilan APINginx menyajikan halaman statis, lalu meneruskan panggilan /api/ ke Gunicorn, middleware Django, dan PostgreSQL.

Semua endpoint Django berada di bawah prefiks /api/, termasuk router SimpleRouter dan api/_allauth/ (backend/config/urls.py:38-47). Django memproses request melewati MIDDLEWARE yang berurutan: SecurityMiddleware, SessionMiddleware, CommonMiddleware, CsrfViewMiddleware, AuthenticationMiddleware, lalu AccountMiddleware, AuditlogMiddleware, dan AxesMiddleware (backend/config/settings.py:47-58). Koneksi database dibuka dengan CONN_MAX_AGE 60 detik (backend/config/settings.py:77-87).

⚠️ Django hanya tahu request itu HTTPS karena header, bukan karena socket. Nginx memasang X-Forwarded-Proto https, dan SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") dipakai saat DEBUG mati (deploy/nginx.conf:21-33, backend/config/settings.py:140-145). Kalau header itu hilang, SECURE_SSL_REDIRECT akan membalas redirect.

Peta rute Nginx

Empat blok location menentukan seluruh perilaku lapisan tepi. Setiap blok memuat ulang /etc/nginx/keamanan.conf, lalu menambahkan Cache-Control sendiri (deploy/keamanan.conf:1-7).

locationPerilakuRujukan
/api/kesehatan/return 404, hanya untuk healthcheck internaldeploy/nginx.conf:16-19
/api/proxy_pass http://api:8000, proxy_read_timeout 60s, header keamanan dari Django disembunyikandeploy/nginx.conf:21-33
/_next/static/Cache-Control "public, max-age=31536000, immutable"deploy/nginx.conf:35-44
/Cache-Control "no-cache", try_files $uri $uri/index.html /404.htmldeploy/nginx.conf:35-44

Blok server menetapkan listen 8443 ssl dengan http2 on, server_tokens off, dan client_max_body_size 6m, sehingga unggahan melewati Nginx sebelum diperiksa Django (deploy/nginx.conf:1-14).

Berkas statis dan jalur build

Konfigurasi Next.js bercabang menurut NODE_ENV. Di produksi dipakai output: "export", trailingSlash: true, dan images: { unoptimized: true }, sehingga hasil build berupa direktori statis tanpa server Next.js (frontend/next.config.ts:7-36). Di development, rewrites() mengarahkan /api/:path* ke API_DEV yang default-nya http://127.0.0.1:8000, jadi next dev tetap bisa memanggil Django tanpa Nginx (frontend/next.config.ts:7-36).

BatasNilaiRujukan
client_max_body_size6mdeploy/nginx.conf:1-14
proxy_read_timeout60sdeploy/nginx.conf:21-33
Gunicorn di compose lokal--workers 2 --access-logfile -compose.yaml:16-33
Gunicorn image produksi--workers 3 --timeout 60backend/Dockerfile:20-24
CONN_MAX_AGE60 detikbackend/config/settings.py:77-87
Masa cache /_next/static/max-age=31536000, immutabledeploy/nginx.conf:35-44

Foto produk dan healthcheck

Foto produk tidak disajikan sebagai berkas statis. MEDIA_ROOT diarahkan ke /data/media (backend/Dockerfile:20-24, backend/config/settings.py:111-112), volume yang sama dipasang ke container api (compose.prod.yaml:22-40), lalu view foto mengembalikan FileResponse dari berkas itu (backend/produk/views.py:98-103). Karena Nginx tidak punya location /media/, byte foto tetap melewati /api/ dan aturan izin Django.

Healthcheck bekerja dari dalam container: python healthcheck.py memanggil http://127.0.0.1:8000/api/kesehatan/ dengan header Host dari DJANGO_ALLOWED_HOSTS (backend/Dockerfile:20-24, backend/healthcheck.py:1-13). Jalur itu sengaja ditutup dari luar oleh return 404 di Nginx, dan dikecualikan dari redirect HTTPS lewat SECURE_REDIRECT_EXEMPT = [r"^api/kesehatan/$"] (deploy/nginx.conf:16-19, backend/config/settings.py:140-145).

Jebakan konfigurasi

⚠️ Port yang dipublikasikan Docker dapat melewati aturan UFW. Itu sebabnya produksi mengikat web ke ${TAILSCALE_IP:?}:443:8443, bukan ke 0.0.0.0 (compose.prod.yaml:1-21).

⚠️ Di luar DEBUG, SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] sengaja gagal saat start kalau variabel itu kosong, dan ALLOWED_HOSTS menjadi daftar kosong bila DJANGO_ALLOWED_HOSTS tidak diisi (backend/config/settings.py:15-24).

⚠️ api dan web berjalan dengan read_only: true serta cap_drop: [ALL]. Setiap penulisan harus menuju volume (media) atau tmpfs /tmp, bukan disk container (compose.prod.yaml:22-40).

Data yang mengalir lewat PostgreSQL, termasuk batch dan mutasi stok yang menopang alur FEFO, dijelaskan di Model Domain dan Constraint Database dan Alur Stok, Batch, dan Opname. Sisi klien yang memakai rute /api/ ini dibahas di Frontend Next.js dan Lapisan Data, daftar endpoint-nya di Endpoint API dan Skema OpenAPI, dan prosedur rilis ketiga container di Deploy, Backup, dan Operasi VPS.