---
title: "Memasang Quire dengan Docker Compose"
description: "Pasang Quire di infrastruktur Anda sendiri dengan Docker Compose."
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/id/llms.txt
> Use this file to discover all available pages before exploring further.

# Memasang Quire dengan Docker Compose

<span id="installing-quire-with-docker-compose"></span>

Ini adalah produk lengkap di satu host: LMS, pekerjaan latar belakangnya,
layanan penyuntingan kolaboratif dan waktu nyata, serta semua layanan opsional
di balik profil. Rancangannya dijelaskan di bagian 2 `docs/architecture/23-ops.md`.

Target lainnya: [Vercel](/id/ops/vercel/) dan [Cloudflare Workers](/id/ops/cloudflare/)
hanya menjalankan tingkat web. Prosedur peningkatan versi ada di [upgrade.md](/id/ops/upgrade/),
sedangkan pencadangan dan latihan pemulihan ada di [backup-restore.md](/id/ops/backup-restore/).

## Yang Anda perlukan <!--quire:what-you-need-->

- Docker Engine 27 atau yang lebih baru dengan plugin Compose 2.30 atau yang
  lebih baru.
- 4 inti CPU dan memori 8 GB untuk tumpukan bawaan; 8 inti dan 16 GB dengan
  `--profile full` (ClamAV sendiri menggunakan sekitar 1,5 GB untuk tanda tangan).
- Nama DNS untuk tingkat web dan nama kedua untuk konten tak tepercaya. Keduanya
  harus merupakan host yang berbeda: paket SCORM dan HTML yang diunggah berjalan
  di origin konten agar tidak pernah dapat membaca cookie LMS.
- Untuk pengujian lokal, `lvh.me` dan `*.localhost` mengarah ke 127.0.0.1,
  sebagaimana digunakan oleh `docker/.env.example`. Layanan `proxy` milik
  tumpukan menyajikan keduanya melalui https dengan otoritas sertifikat lokal,
  jadi tidak perlu memasang apa pun lagi (lihat "TLS").
- Port 80 dan 443 harus tersedia di host (`QUIRE_PROXY_HTTP_PORT` dan
  `QUIRE_PROXY_HTTPS_PORT` dapat memindahkannya).

## Menjalankan pertama kali <!--quire:first-run-->

```sh
QUIRE_APP_ORIGIN=https://learn.example.org \
QUIRE_CONTENT_ORIGIN=https://content.example-content.org \
QUIRE_SETUP_ADMIN_EMAIL=you@example.org \
  docker/scripts/init-env.sh
docker compose -f docker/compose.yaml up -d --build
docker compose -f docker/compose.yaml logs init
```

`docker/scripts/init-env.sh` membuat `docker/.env` dari `docker/.env.example`
dengan semua rahasia yang sudah dibuat (kata sandi basis data, kunci
penandatanganan dan utama, pasangan kunci peluncuran konten), serta kunci
penandatanganan titik pemeriksaan audit di
`docker/secrets/audit-signing-key.pem`, yang dipasang Compose ke worker sebagai
rahasia. Skrip ini hanya memerlukan `sh`, `awk`, dan `openssl`, serta menolak
menimpa `docker/.env` yang sudah ada. Salin kedua berkas tersebut ke luar host:
tanpa `QUIRE_MASTER_KEY`, basis data yang dipulihkan tidak dapat mendekripsi
kredensial tersimpannya. Untuk mengisi berkas secara manual, jalankan
`cp docker/.env.example docker/.env`; berkas itu menjelaskan cara membuat setiap
rahasia.

Kedua origin harus menggunakan `https`: layanan konten menolak http biasa di
lingkungan produksi, dan keduanya tidak boleh berbagi domain yang dapat
diregistrasikan. Layanan `proxy` mengakhiri TLS untuk keduanya (lihat "TLS");
`init-env.sh` menolak origin `http://`.

Tumpukan dimulai dalam urutan tetap, dan setiap langkah menunggu langkah
sebelumnya:

1. `postgres` menjadi sehat. Pada permulaan pertama, skrip inisialisasinya
   (`docker/postgres/init/90-passwords.sh`) menyetel empat kata sandi peran.
2. `migrate` menerapkan semua migrasi dan menyiapkan antrean pekerjaan di basis
   data kontrol serta di setiap basis data penyewa khusus, memeriksa kesesuaian
   semuanya, lalu keluar (docs/ops/upgrade.md). Migrasi dijalankan setiap kali
   mulai dan bersifat idempoten, jadi peningkatan versi cukup dengan image baru
   dan memulai ulang.
3. `init` (`apps/web/src/first-run.ts`) mendaftarkan basis data aplikasi di
   bawah `QUIRE_DATABASE_ID` dan, jika `QUIRE_SETUP_ADMIN_EMAIL` disetel,
   membuat organisasi pertama beserta administratornya. Alamat masuk dan kata
   sandi yang dibuat dicetak sekali di `docker compose logs init`.
4. `web`, `content`, `worker`, `scheduler`, `collab`, dan `centrifugo` dimulai.
5. `proxy` dimulai setelah `web` dan `content` sehat.

Buka `https://demo.` diikuti domain aplikasi Anda (log `init` mencetak alamat
masuk yang tepat), lalu masuk. Pada instalasi lokal, percayai dahulu otoritas
sertifikat proxy (lihat "TLS"). Ganti kata sandi yang dibuat di
`/account/security`.

Proses yang dimulai tanpa rahasia wajib akan menolak berjalan dan menyebutkan
pengaturan yang hilang di lognya. Tidak ada proses yang berjalan dengan
konfigurasi setengah jadi.

## Layanan dan profil <!--quire:services-and-profiles-->

| Layanan | Profil | Fungsinya |
| --- | --- | --- |
| postgres | selalu | Basis data (PostgreSQL 18 dengan pgvector, dibuat dari `docker/postgres.Dockerfile`), WAL diarsipkan sejak boot pertama |
| migrate, init | selalu | Sekali jalan: migrasi, lalu penyiapan pertama |
| web | selalu | LMS, pada `QUIRE_HTTP_PORT` (8080) |
| content | selalu | Origin konten tak tepercaya, pada `QUIRE_CONTENT_PORT` (8081) |
| worker | selalu | Pekerjaan latar belakang: email, laporan, pemrosesan berkas, webhook |
| scheduler | selalu | Pekerjaan berulang: mendaftarkan 64 jadwal waktu jalan dan menyerahkannya kepada worker; satu pemimpin pada satu waktu |
| collab | selalu | WebSocket penyuntingan kolaboratif, pada `QUIRE_COLLAB_HTTP_PORT` (1234) |
| centrifugo | selalu | Distribusi waktu nyata, pada `QUIRE_REALTIME_PORT` (8000) |
| proxy | selalu | Caddy, pintu masuk TLS pada port 80 dan 443 (lihat "TLS") |
| valkey | `cache` | Cache dan pembatasan laju |
| clamav | `scan` | Pemindaian malware pada unggahan |
| gotenberg | `preview` | Pratinjau Office ke PDF, perenderan sertifikat |
| imgproxy | `images` | Gambar yang diubah ukuran dan formatnya |
| transcoder | `video` | Image worker dengan ffmpeg berlisensi LGPL saja, untuk varian video |
| seaweedfs | `storage` | Penyimpanan objek yang kompatibel dengan S3 di host ini |
| otelcol | `observability` | Kolektor OpenTelemetry |
| mailpit | `devmail` | Menangkap semua email keluar untuk mencoba Quire |
| backup | `backup` | Cadangan dasar sekali jalan; lihat backup-restore.md |
| backup-scheduler, backup-offsite | `backup` | Cadangan dasar setiap `QUIRE_BACKUP_INTERVAL_HOURS`, dan salinan terenkripsi di luar host dengan latihan verifikasi mingguan |
| h5p | `h5p` | Image alat H5P LTI 1.3 yang Anda sediakan di `QUIRE_H5P_IMAGE`, pada `QUIRE_H5P_PORT` (8090); lihat "Menghubungkan penyedia H5P" |

`--profile full` memulai semua layanan opsional kecuali `backup` dan `h5p`.
Mulai satu profil dengan `docker compose -f docker/compose.yaml --profile scan up -d`.
Tanpa layanan opsional, Quire tetap berfungsi dan memberi tahu apa yang tidak
tersedia: tanpa pemindai, unggahan disimpan tanpa pemindaian dan administrator
diberi tahu; tanpa Gotenberg, berkas dapat diunduh alih-alih menampilkan
pratinjau; tanpa transcoder, video diputar sebagai berkas aslinya.

Setiap image pihak ketiga dan kewajiban lisensinya tercantum di
`docker/third-party-containers.yaml`.

### Menghubungkan penyedia H5P <!--quire:connecting-an-h5p-provider-->

Quire tidak menyematkan atau mengirimkan runtime H5P maupun sidecar (ADR 0019).
Jika Anda menggunakan H5P, sediakan langganan yang dihosting sendiri atau
operasikan instans H5P mandiri yang dihosting sendiri, terpisah dari Quire.
Daftarkan penyedia tersebut sebagai alat eksternal LTI 1.3 dan tambahkan
kontennya ke kursus sebagai aktivitas alat. Quire bertukar nilai dan progres
aktivitas/penilaian melalui LTI Assignment and Grade Services (AGS). Jika
penyedia juga mengirim pernyataan xAPI, konfigurasikan hal itu secara terpisah
untuk penyimpanan pernyataan xAPI Quire; pertukaran nilai/progres AGS tidak
mengirim pernyataan xAPI. Impor Moodle melaporkan aktivitas H5P sebagai aktivitas
yang memerlukan koneksi alat LTI. Penyedia tetap bertanggung jawab atas runtime
H5P, pembuatan materi, bank konten, dan riwayat percobaan.

Untuk menjalankan instans mandiri milik Anda di host ini, setel
`QUIRE_H5P_IMAGE` ke imagenya dan mulai profil `h5p`. Compose memublikasikannya
pada `QUIRE_H5P_PORT` (8090) dan menyimpan datanya di volume `h5p-data`; image
dan kewajiban yang menyertainya tetap menjadi tanggung jawab Anda.

## Pengaturan <!--quire:settings-->

Setiap proses membaca `docker/.env`. Templat `docker/.env.example` mencantumkan
setiap pengaturan beserta nilai bawaannya. Kelompok pengaturan:

### Alamat <!--quire:addresses-->

| Pengaturan | Arti |
| --- | --- |
| `QUIRE_APP_ORIGIN` | Alamat publik LMS, misalnya `https://learn.example.com` |
| `QUIRE_CONTENT_ORIGIN` | Origin konten, menggunakan host berbeda |
| `QUIRE_PLATFORM_DOMAINS` | Domain tempat organisasi berada, dipisahkan koma |
| `QUIRE_MARKETING_ORIGIN` | Optional. The marketing site, default `https://quirelms.com`. The only origin the waitlist form (`POST /api/waitlist`, `POST /waitlist`) accepts and redirects to. Comma separated; a `www.` variant is allowed only if listed |
| `QUIRE_DEPLOY_TARGET` | Di sini nilainya `compose`. Lihat panduan lain untuk `vercel` dan `cloudflare` |
| `QUIRE_TRUSTED_PROXY_CIDRS` | Proxy yang nilai `X-Forwarded-For`-nya dipercaya |

### Rahasia <!--quire:secrets-->

| Pengaturan | Arti |
| --- | --- |
| `QUIRE_SECRET_KEY` | Menandatangani sesi dan token. 64 karakter heksadesimal |
| `QUIRE_MASTER_KEY` | Membungkus kredensial tersimpan seperti rahasia SSO dan webhook. 32 byte, base64. Tingkat web dan worker memerlukan nilai yang sama. Rotasi: [key-rotation.md](/id/ops/key-rotation/) |
| `QUIRE_MASTER_KEY_VERSION` | Label versi kunci utama, `v1` jika tidak disetel. Naikkan saat merotasi |
| `QUIRE_MASTER_KEY_RETIRED` | Kunci utama sebelumnya yang masih diperlukan untuk membaca data yang dibungkus dengannya, sebagai `v1=<base64>`. Hapus setelah rotasi selesai tanpa nilai yang belum terselesaikan |
| `QUIRE_COLLAB_SIGNING_KEY` | Dipakai bersama oleh web dan collab untuk menandatangani token penyuntingan |
| `QUIRE_BACKUP_SIGNING_KEY` | Menandatangani cadangan kursus (opsional) |

Simpan salinan `QUIRE_MASTER_KEY` di tempat selain host ini. Basis data yang
dipulihkan tanpanya tidak dapat mendekripsi kredensial di dalamnya.

### Basis data <!--quire:database-->

| Pengaturan | Arti |
| --- | --- |
| `POSTGRES_PASSWORD` | Superuser, digunakan oleh kontainer dan proses pencadangan |
| `QUIRE_DB_APP_PASSWORD`, `QUIRE_DB_MIGRATOR_PASSWORD`, `QUIRE_DB_REPORT_PASSWORD`, `QUIRE_DB_AUDIT_PASSWORD` | Kata sandi peran, disetel saat pertama kali dimulai |
| `DATABASE_URL` | Peran aplikasi. Keamanan tingkat baris berlaku untuk setiap kuerinya |
| `DATABASE_MIGRATOR_URL`, `QUIRE_MIGRATION_URL` | Peran migrator untuk `migrate` dan `init` |
| `QUIRE_SUPERUSER_URL` | Hanya digunakan saat penyiapan pertama |
| `QUIRE_REPORT_DATABASE_URL` | Peran laporan hanya-baca, untuk laporan dan pembuat laporan |
| `QUIRE_AUDIT_DATABASE_URL` | Peran audit, untuk konsol audit dan ekspor SIEM |
| `QUIRE_DATABASE_ID` | UUID apa pun yang tetap sepanjang masa instalasi |

Kata sandi peran hanya diterapkan saat volume basis data dibuat pertama kali.
Untuk mengubahnya kemudian, gunakan `ALTER ROLE`, lalu perbarui URL yang sesuai.

`QUIRE_REPORT_DATABASE_URL` digunakan untuk basis data fisik yang dikonfigurasi
oleh `DATABASE_URL`. Untuk basis data fisik terdaftar lainnya, setel URL koneksi
`quire_report` tersendiri di lingkungan web dan worker, lalu masukkan nama
variabel tersebut di kolom **Variabel lingkungan pelaporan** pada basis data itu
sebagai `env:NAME`. Referensi tersebut harus menunjuk ke basis data yang sama
dengan koneksi aplikasinya, idealnya replika baca. Setiap permukaan pelaporan
mengikuti penyewa ke koneksi laporan milik basis datanya sendiri: pembuat laporan
dan laporan tersimpan, pengiriman terjadwal, ekspor laporan, analitik, log audit,
sumber daya audit REST, serta pencarian audit oleh asisten. Tidak satu pun
menggunakan URL laporan milik basis data lain. Jika basis data tidak memiliki
koneksi laporan, laporan biasa berjalan pada koneksi aplikasinya sendiri,
sedangkan analitik dan setiap pembacaan audit menolak permintaan dan menjelaskan
alasannya, karena peran aplikasi tidak dapat membaca jejak audit.

### Driver <!--quire:drivers-->

| Pengaturan | Rilis ini | Catatan |
| --- | --- | --- |
| `QUIRE_STORAGE_DRIVER` | `local` (bawaan), `s3`, atau `azure` | `local` menyimpan berkas di volume `files`. `s3` mencakup AWS S3, interoperabilitas R2 dan GCS, serta penyimpanan lain yang kompatibel dengan S3, dengan unggahan multipart yang dapat dilanjutkan |
| `QUIRE_REALTIME_DRIVER` | `inprocess` (bawaan), `sse`, `centrifugo`, atau `durable_objects` | `inprocess` cocok untuk satu kontainer web; gunakan `centrifugo` atau `sse` jika ada beberapa kontainer |
| `QUIRE_CACHE_DRIVER` | `memory` (bawaan), `postgres`, atau `valkey` | `memory` berlaku per proses; gunakan `valkey` atau `postgres` agar pembatasan laju berlaku di semua kontainer |
| `QUIRE_VIDEO_DRIVER` | `ffmpeg` (bawaan) atau `progressive_mp4` | Atau penyedia terhosting: Cloudflare Stream, Mux, atau Bunny, melalui kuncinya |
| `QUIRE_IMAGE_DRIVER` | `noop` (bawaan), `imgproxy`, atau `cloudflare` | `noop` menyajikan setiap gambar dengan ukuran aslinya. `imgproxy` memerlukan profil `images` dan pengaturan di bawah; `cloudflare` menggunakan Cloudflare Images |
| `QUIRE_MEETING_PROVIDER` | `bbb`, `zoom`, `teams`, `meet`, `jitsi`, atau `in_process` | Nilai bawaan platform untuk sesi langsung. Jika tidak disetel, sesi langsung menyatakan belum dikonfigurasi sampai organisasi menghubungkan akunnya sendiri melalui Integrasi, Penyedia sesi langsung. Akun milik organisasi selalu diutamakan daripada nilai ini. Pengaturan masing-masing penyedia (`BBB_URL` dan `BBB_SECRET`, variabel `ZOOM_*`, `TEAMS_*`, `GOOGLE_MEET_*`, dan `JITSI_*`) hanya dibaca untuk penyedia yang disebutkan di sini |
| `QUIRE_MEETING_REGIONS` | Daftar koma berisi `eu`, `uk`, `us` | Lokasi pemrosesan rapat oleh penyedia bawaan platform. Jika tidak disetel, wilayahnya tidak dibandingkan dengan organisasi yang dipasangi wilayah tertentu, seperti sebelumnya. Akun organisasi sendiri mencantumkan wilayahnya di halamannya |

Nilai driver yang tidak disertakan dalam rilis ini akan ditolak saat tingkat web
dimulai, dengan nama pengaturannya, alih-alih diam-diam diganti dengan nilai
bawaan.
### Gambar <!--quire:images-->

Halaman meminta gambar dalam empat ukuran tetap melalui
`/api/files/{id}/image/{size}`. Endpoint ini memeriksa akses yang sama seperti
berkasnya, lalu mengalihkan permintaan ke layanan gambar. Setiap organisasi
dapat meminta pembuatan paling banyak `QUIRE_IMAGE_SPECS_PER_HOUR` (bawaan 2000)
pasangan gambar dan ukuran baru per jam; ukuran yang sudah dibuat pada jam itu
tidak dihitung. Gunakan `valkey` atau `postgres` untuk `QUIRE_CACHE_DRIVER` jika
ada lebih dari satu kontainer web agar batasnya berlaku di semua kontainer.

| Pengaturan | Driver | Catatan |
| --- | --- | --- |
| `IMGPROXY_URL` | `imgproxy` | Alamat imgproxy yang dijangkau browser, misalnya `https://images.example.org`. Profil `images` memublikasikannya di `QUIRE_IMAGES_PORT` (8082) |
| `IMGPROXY_KEY`, `IMGPROXY_SALT` | `imgproxy` | String heksadesimal dengan nilai yang sama seperti saat imgproxy dijalankan. Buat masing-masing dengan `openssl rand -hex 32`. Quire menandatangani setiap alamat gambar dengannya, sehingga imgproxy hanya merender permintaan yang diajukan Quire |
| `QUIRE_IMAGE_SOURCE_ORIGIN` | `imgproxy` dengan penyimpanan lokal | Lokasi imgproxy mengambil gambar asli. Compose menyetel `http://web:3000`. Dengan penyimpanan `s3` atau `azure`, imgproxy mengambil dari bucket dan pengaturan ini tidak digunakan |
| `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_IMAGES_TOKEN`, `CLOUDFLARE_IMAGES_ACCOUNT_HASH` | `cloudflare` | Token API dengan izin edit Images dan hash akun dari Images, Developer resources. Aktifkan varian fleksibel untuk akun tersebut |
| `CLOUDFLARE_IMAGES_SIGNING_KEY` | `cloudflare` | Opsional. Jika disetel, gambar bersifat privat dan setiap alamat ditandatangani serta kedaluwarsa. Tanpanya, gambar bersifat publik dengan alamat yang diturunkan dari `QUIRE_SECRET_KEY` dan tidak dapat ditebak |

Cloudflare Images menyimpan salinannya sendiri dari setiap gambar asli yang
disajikan. Saat berkas dihapus, worker menghapus salinan tersebut sebelum
menghapus aslinya.

### Antrean <!--quire:queue-->

Pekerjaan latar belakang menggunakan pg-boss di basis data Postgres yang sama,
jadi tidak ada layanan antrean yang perlu dijalankan ataupun dikonfigurasi.
Pekerjaan dimasukkan ke antrean dalam transaksi yang sama dengan perubahan
pemicunya, sehingga kerusakan tidak akan menghilangkan pekerjaan ataupun
mengirimkannya dua kali. `QUIRE_QUEUE_DRIVER` bernilai bawaan `pgboss` di sini;
`vercel` dan `cloudflare` memindahkan hanya pengiriman notifikasi ringan dan
webhook ke antrean milik platform. Panduan Vercel dan Cloudflare menjelaskan
antrean tersebut dan cara tingkat webnya memasukkan pekerjaan.

### Email <!--quire:email-->

Setel salah satu dari:

- `QUIRE_EMAIL_PROVIDER_CONFIG`: objek JSON yang menyebutkan penyedia HTTP dan
  kredensialnya, misalnya `{"provider":"postmark","token":"..."}`. Postmark,
  Amazon SES, Mailgun, SendGrid, dan Resend didukung.
- `QUIRE_SMTP_URL`: `smtp://user:password@host:587`. Hanya untuk target ini;
  target tanpa server memblokir SMTP.

`QUIRE_MAIL_FROM` adalah alamat pengirim. Untuk mencoba Quire, mulai profil
`devmail`, setel `QUIRE_SMTP_URL=smtp://mailpit:1025`, lalu baca email di
`http://localhost:8025`.

### Layanan opsional <!--quire:optional-services-->

| Pengaturan | Dengan profil |
| --- | --- |
| `CLAMAV_URL=tcp://clamav:3310` | `scan` |
| `GOTENBERG_URL=http://gotenberg:3000` | `preview` |
| `IMGPROXY_KEY`, `IMGPROXY_SALT` | `images` |
| `VALKEY_URL=redis://valkey:6379` | `cache` |
| `QUIRE_OPENSEARCH_URL` atau `QUIRE_MEILISEARCH_URL` | Pencarian eksternal; jika tidak, gunakan pencarian teks lengkap Postgres |
| `QUIRE_BREACH_CHECK_PROVIDER=off`, `QUIRE_BREACH_CHECK_URL` | Pemeriksaan kebocoran kata sandi. Secara bawaan aktif melalui `api.pwnedpasswords.com` (hanya prefiks hash lima karakter yang dikirim); `off` menonaktifkannya, dan URL menunjuk ke API rentang yang Anda kelola |

### Observabilitas <!--quire:observability-->

`OTEL_EXPORTER_OTLP_ENDPOINT` menentukan kolektor tujuan pengiriman trace dan
metrik oleh setiap proses. Dengan profil `observability`, nilainya
`http://otelcol:4318`, dan `docker/otel-collector.yaml` adalah tempat untuk
menambahkan eksportir backend Anda. Jika disetel, proses web, worker, scheduler,
content, dan collab mengekspor span melalui OTLP/HTTP (permintaan web, transaksi
basis data penyewa, pekerjaan worker, dan panggilan keluar), serta mengirim
metrik ke endpoint yang sama setiap menit (`OTEL_METRICS_EXPORTER=none`
menonaktifkannya). `OTEL_TRACES_SAMPLER_ARG` mengatur proporsi trace yang
disimpan. Log dikirim ke keluaran standar pada `LOG_LEVEL`, dan dirotasi oleh
Compose. Trace tidak pernah memuat data pribadi.

### Egress wilayah (residensi data UE) <!--quire:regional-egress-eu-data-residency-->

`QUIRE_REGION=eu` menyatakan bahwa tumpukan melayani organisasi Uni Eropa.
Worker kemudian membatasi setiap permintaan keluar untuk organisasi yang
dipasangi wilayah UE ke daftar izin (21-compliance.md bagian 8.1). Daftar itu
mencakup host yang dinyatakan oleh layanan terkonfigurasi untuk wilayah tersebut
(endpoint penyimpanan, penyedia email, penyedia video terhosting, target
penyimpanan milik organisasi, penyedia AI, dan akun email), host layanan yang
memiliki derogasi aktif, serta host yang Anda cantumkan di
`QUIRE_EGRESS_ALLOW_HOSTS`. Permintaan ke host publik lain ditolak sebelum
dikirim, penolakan dicatat di jejak audit organisasi sebagai
`privacy/egress_refused`, dan ditampilkan di Kepatuhan, Residensi data.

| Pengaturan | Nilai | Dampak |
| --- | --- | --- |
| `QUIRE_EGRESS_ALLOW_HOSTS` | Daftar nama host dipisahkan koma, atau `*.example.org` untuk semua subdomain | Host tambahan yang boleh dijangkau organisasi UE. Endpoint webhook, xAPI dan SIEM, umpan blog, serta host Amazon SES harus dicantumkan di sini karena merupakan pilihan organisasi sendiri dan tidak dinyatakan oleh layanan mana pun. Loopback, alamat privat, dan nama satu label seperti `web` atau `clamav` merupakan jaringan Anda sendiri dan tidak pernah diperiksa |

Organisasi UK dan US tidak dibatasi daftar host; pemeriksaan wilayah layanan
tetap berlaku. Setel daftar tersebut pada worker; halaman admin membacanya di
tingkat web untuk menampilkan daftar izin, jadi letakkan di `docker/.env` yang
dibaca oleh setiap layanan.

Pemeriksaan aplikasi memberikan kesalahan yang jelas dan entri audit, tetapi
bukan jaminannya: kode bisa saja salah. Jaminannya adalah jaringan. Compose tidak
menegakkannya untuk Anda. Untuk tumpukan regional, tempatkan layanan `worker`
dan `web` di jaringan `internal: true` yang satu-satunya rute keluarnya adalah
proxy egress (misalnya Squid atau kontainer tinyproxy) yang mengizinkan host
yang sama dengan `QUIRE_EGRESS_ALLOW_HOSTS`, ditambah host layanan yang Anda
konfigurasikan, lalu setel `HTTPS_PROXY` untuk layanan tersebut. Halaman
residensi mencantumkan host persis yang diizinkan aplikasi, sehingga kedua daftar
dapat dibandingkan.

## Kesehatan <!--quire:health-->

| Endpoint | Arti |
| --- | --- |
| `/healthz` | Liveness: proses merespons. Pemeriksaan kesehatan Compose menggunakan endpoint ini |
| `/readyz` | Kesiapan: dependensi dapat dijangkau, dan setiap layanan opsional dilaporkan sudah atau belum dikonfigurasi. Arahkan load balancer Anda ke sini |

`docker compose -f docker/compose.yaml ps` menampilkan status kesehatan setiap
layanan.

## TLS <!--quire:tls-->

Layanan `proxy` (Caddy, Apache-2.0, `docker/caddy/Caddyfile`) merupakan bagian
dari tumpukan bawaan. Layanan ini merespons pada port 80 dan 443 serta
mengarahkan:

| Host atau jalur | Diteruskan ke |
| --- | --- |
| `QUIRE_PROXY_CONTENT_HOST` | `content` |
| `QUIRE_PROXY_APP_HOST`, setiap subdomain penyewa dan domain kustom | `web` |
| `/_collab/` pada host tersebut | `collab` (WebSocket, `QUIRE_COLLAB_URL`) |
| `/_realtime/connection/` pada host tersebut | WebSocket klien `centrifugo`; API server-nya tidak pernah diekspos |
| `/_images/` pada host tersebut | `imgproxy`, dengan profil `images` (`IMGPROXY_URL`) |

`init-env.sh` menurunkan `QUIRE_PROXY_APP_HOST`, `QUIRE_PROXY_CONTENT_HOST`,
`QUIRE_PROXY_HTTPS_PORT`, `QUIRE_COLLAB_URL`, dan `IMGPROXY_URL` dari kedua
origin agar nilainya selalu selaras. Jika Anda mengubah origin secara manual,
ubah semua pengaturan terkait secara bersamaan.

Sertifikat mengikuti `QUIRE_PROXY_TLS`:

- `internal` (nilai bawaan): otoritas sertifikat milik Caddy untuk `localhost`,
  `*.localhost`, dan `lvh.me`. Percayai sertifikat akarnya sekali, lalu buka:

  ```sh
  docker compose -f docker/compose.yaml cp \
    proxy:/data/caddy/pki/authorities/local/root.crt ./quire-local-ca.crt
  ```

  Tambahkan `quire-local-ca.crt` ke penyimpanan sertifikat tepercaya sistem atau
  browser. Untuk `curl`, berikan dengan `--cacert`.
- Alamat email: sertifikat ACME otomatis (Let's Encrypt, lalu ZeroSSL) untuk
  nama host sungguhan. DNS kedua origin dan setiap host penyewa harus mengarah
  ke sini, dan port 80 serta 443 harus dapat dijangkau dari internet.

Sertifikat host penyewa diterbitkan sesuai permintaan, pada kunjungan pertama,
dan hanya setelah web mengonfirmasi nama tersebut milik instalasi ini
(`/tls-allowed`, diminta melalui jaringan Compose). Sertifikat wildcard dan
plugin penyedia DNS tidak diperlukan, dan orang asing yang mengarahkan nama ke
host ini tidak dapat memaksanya meminta sertifikat. Sertifikat dan otoritas
lokal berada di volume `caddy-data`; jika Anda menggunakan `internal`, cadangkan
bersama data lainnya.

Web hanya memercayai `X-Forwarded-For` dari proxy: proxy memiliki alamat tetap
(`QUIRE_PROXY_ADDRESS`, bawaan `172.29.64.10`) pada subnet tetap
(`QUIRE_COMPOSE_SUBNET`), dan `QUIRE_TRUSTED_PROXY_CIDRS` mencantumkan alamat
tersebut. Jika subnet berbenturan dengan jaringan di host, ubah keduanya dan
jalankan `docker compose down` sebelum `up`.

## Di balik reverse proxy Anda sendiri <!--quire:behind-your-own-reverse-proxy-->

Untuk menggunakan load balancer atau proxy yang sudah Anda jalankan, jangan
sertakan `proxy` (`docker compose up -d --scale proxy=0`) dan akhiri TLS di
depan `web` (8080), `content` (8081), `collab` (1234, WebSocket), dan
`centrifugo` (8000, WebSocket). Setel alamat publik di `QUIRE_APP_ORIGIN`,
`QUIRE_CONTENT_ORIGIN`, serta `QUIRE_COLLAB_URL` (`wss://`), dan rentang alamat
proxy Anda di `QUIRE_TRUSTED_PROXY_CIDRS`.

## Pemecahan masalah <!--quire:troubleshooting-->

- `init` keluar dengan pesan "QUIRE_DATABASE_ID is not a UUID": setel dengan
  `uuidgen`.
- `web` dimulai ulang dengan pesan "did not start on compose": log mencantumkan
  setiap pengaturan yang tidak dapat dipenuhi dan nilai penggantinya.
- Mengubah kata sandi peran di `.env` setelah permulaan pertama tidak berdampak:
  skrip init hanya berjalan sekali. Gunakan `ALTER ROLE`.
- Unggahan gagal dengan kesalahan pemindaian saat `CLAMAV_URL` disetel: ClamAV
  mengunduh tanda tangannya pada permulaan pertama, dan proses ini memerlukan
  beberapa menit.

Source: https://docs.quirelms.com/id/ops/install/index.mdx
