Kita baru saja menutup Section Aplikasi Multi-Container dengan sebuah stack development yang utuh: reverse proxy di depan, aplikasi dan database di belakang, ditambah cache yang menyertai keduanya, semuanya dinyalakan dari satu berkas compose.yaml. Sepanjang section itu database memang hadir dan bekerja, tetapi perlakuannya singkat — satu baris image, segelintir environment variable, satu volume, dan sebuah healthcheck. Kita belum pernah benar-benar menghidupkannya sendiri, membaca riwayat startnya, atau berbicara langsung dengannya lewat klien resminya. Section ini membuka kotak hitam itu: satu per satu kita menjalankan PostgreSQL, MySQL, MariaDB, dan Redis dengan docker run telanjang, membedah log startup masing-masing, membuat tabel dari dalam container, lalu membuktikan bahwa data yang kita tanam benar-benar bertahan melewati kematian container. Proyek katalog Budi kembali menjadi kendaraan kita — user katalog, database katalog, dan tabel produk yang menampung kopi-kopi pilihan.
Kenapa Database di Container #
Sebelum mengetik perintah pertama, mari kita luruskan dulu alasannya. Memasang database lewat manajer paket host memang bisa, tetapi tiga sifat container menjadikannya pasangan yang lebih sehat untuk database:
- Instalasi bersih — database tidak menyentuh satu paket host pun; tidak ada service systemd baru, tidak ada berkas konfigurasi tersebar di
/etc, dan tidak ada user sistem tambahan yang menumpuk di laptop. - Versi konsisten — setiap proyek menuliskan versi database yang dipin di dalam dokumentasinya; proyek lama boleh tetap memakai PostgreSQL 16 sementara proyek baru menjalankan 18, keduanya hidup berdampingan tanpa saling menginjak.
- Buang gampang —
docker rm -fditambahdocker volume rmmengembalikan mesin ke keadaan semula dalam hitungan detik; mencoba skema baru, gagal, lalu mulai lagi dari nol menjadi ritual yang murah, bukan petualangan apt yang menakutkan.
Sifat ketiga itulah yang paling mengubah kebiasaan Budi. Ia dulu menunda bereksperimen karena takut “mengotori” instalasi PostgreSQL di laptop; kini satu baris perintah bisa melahirkan database segar, dan satu baris lain memusnahkannya tanpa bekas.
Menjalankan PostgreSQL untuk Proyek katalog #
Inilah perintah inti chapter ini — satu docker run yang menyalakan PostgreSQL 18 dengan identitas lengkap proyek katalog:
$ docker run -d --name db \
-e POSTGRES_USER=katalog \
-e POSTGRES_PASSWORD=rahasia123 \
-e POSTGRES_DB=katalog \
-v db-data:/var/lib/postgresql/data \
postgres:18-alpineOutput yang Dihasilkan:
ec87a50cb18149a537598de1c0998c6897df7353c330f4a617e48c9edafaea10Image postgres:18-alpine sudah tersedia di lokal sejak Section Penyimpanan Data, jadi tidak ada proses penarikan — daemon langsung mencetak ID container 64 karakter dan pulang. Mari kita telaah perintah ini bagian demi bagian:
-d— mode detached; server PostgreSQL berjalan di latar belakang dan terminal kita tetap bebas, kebiasaan yang melekat sejak Chapter Menjalankan Container Pertama.--name db— nama pendek yang akan kita sebut di setiapdocker exec,docker logs, dandocker rm; pilihan nama yang persis sama dengan kebiasaan penamaan service pada Section Docker Compose membuat otot ingatan kita dipakai ulang.-e POSTGRES_USER=katalog— nama superuser yang diciptakan saat inisialisasi; tanpa variabel ini entrypoint memakai bawaanpostgres. Mekanisme suntikan-esendiri sudah kita bedah tuntas pada Chapter Environment Variable.-e POSTGRES_PASSWORD=rahasia123— kata sandi superuser; satu-satunya variabel yang benar-benar wajib bagi image ini — tanpa sandi, container postgres menolak menyala, persis peringatan yang kita saksikan pada Chapter Use Case Docker.-e POSTGRES_DB=katalog— database awal yang dibuat sekaligus saat inisialisasi; tanpa variabel ini kita harus menjalankanCREATE DATABASEsendiri setelah container siap.-v db-data:/var/lib/postgresql/data— volume bernama dipasang pada direktori data resmi image postgres; seluruh cluster — tabel, indeks, konfigurasi — menulis ke volume ini, bukan ke writable layer container. Mekanisme di balik flag ini adalah materi Chapter Volume.postgres:18-alpine— tag dipin ke seri mayor 18, varian Alpine yang kecil; versi patch yang terpasang saat riset Course ini adalah 18.4.
| Flag | Nilai | Peran |
|---|---|---|
-d | — | berjalan di latar belakang |
--name | db | identitas container untuk exec, logs, stop, rm |
-e POSTGRES_USER | katalog | superuser yang diciptakan saat inisialisasi |
-e POSTGRES_PASSWORD | rahasia123 | kata sandi superuser — wajib untuk image postgres |
-e POSTGRES_DB | katalog | database awal yang dibuat saat inisialisasi |
-v db-data:/var/lib/postgresql/data | volume:target | rumah cluster PostgreSQL yang berumur lebih panjang dari container |
Membaca Log Startup #
Database butuh beberapa detik sebelum benar-benar siap, dan log startup adalah tempat paling jujur untuk mengikutinya. Riwayat lengkap sejak container lahir terbaca lewat docker logs — kebiasaan dari Chapter Melihat Log Container:
$ docker logs dbOutput yang Dihasilkan:
The files belonging to this database system will be owned by user "katalog".
This user must also own the server process.
initdb: warning: enabling "trust" authentication for local connections only.
You can change this by editing pg_hba.conf or using the option -A (or
--auth-local and --auth-host) the next time you run initdb.
...
2026-10-13 20:41:18.402 UTC [1] LOG: starting PostgreSQL 18.4 (Debian 18.4-1.pgdg130+1) on x86_64-pc-linux-musl, compiled by gcc (Alpine 14.2.0) 14.2.0, 64-bit
2026-10-13 20:41:18.403 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2026-10-13 20:41:18.403 UTC [1] LOG: listening on IPv6 address "::", port 5432
2026-10-13 20:41:18.414 UTC [1] LOG: database system is ready to accept connectionsBaris-baris initdb di bagian tengah dipotong agar ringkas. Mari kita telaah baris-baris yang tampil:
owned by user "katalog"— jejakPOSTGRES_USER: seluruh berkas cluster dimiliki oleh user internal bernama katalog, bukan bawaan postgres; keputusan kita di flag-etercetak hitam di atas putih.- Peringatan
trustuntuk koneksi lokal —initdbmenegaskan bahwa koneksi dari dalam container saja dibiarkan tanpa sandi; koneksi dari luar tetap memakai autentikasi sandi, jadi peringatan ini bukan lubang keamanan bagi pola kita. starting PostgreSQL 18.4 (Debian 18.4-1.pgdg130+1) ... musl— banner versi server; sisipan(Debian ...)menandakan sumber paket yang dipakai membangun binary, sementarax86_64-pc-linux-musldangcc (Alpine 14.2.0)menegaskan binary itu berjalan di libc musl khas varian Alpine.listening on IPv4 address "0.0.0.0", port 5432— server mendengarkan di semua antarmuka di dalam network namespace container;0.0.0.0di sini tidak berarti host kita terbuka, karena tanpa-ptidak ada jembatan port ke host — kita bedah implikasinya beberapa bagian di bawah.database system is ready to accept connections— kalimat kunci: sejak baris ini PostgreSQL sanggup menerima koneksi. Setelahnya database yang sehat justru membisu.[1]dan stempel waktu UTC — seluruh baris berasal dari proses bernomor PID 1; PostgreSQL berjalan sebagai proses utama container, dan waktunya tercatat dalam UTC.
Sesi Interaktif dengan psql #
Image postgres membawa psql, klien baris perintah resminya, sehingga kita tidak perlu memasang apa pun di host. Pola docker exec -it sudah akrab sejak Chapter Menjalankan Perintah dalam Container — flag -i menjaga stdin terbuka dan -t menyiapkan terminal semu:
$ docker exec -it db psql -U katalogOutput yang Dihasilkan:
psql (18.4)
Type "help" for help.
katalog=#psql membuka koneksi sebagai user katalog, dan karena kita tidak menyebut -d, psql memakai database yang namanya sama dengan nama user — itulah sebabnya prompt langsung berbunyi katalog=#. Mari kita ajak sesi ini berkeliling, dari melihat daftar database sampai menanam baris pertama tabel produk:
katalog=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | ICU Locale | Locale Provider | Access privileges
-----------+----------+----------+-----------+-----------+------------+-----------------+-------------------
katalog | katalog | UTF8 | C.UTF-8 | C.UTF-8 | | libc |
postgres | katalog | UTF8 | C.UTF-8 | C.UTF-8 | | libc |
template0 | katalog | UTF8 | C.UTF-8 | C.UTF-8 | | libc |
template1 | katalog | UTF8 | C.UTF-8 | C.UTF-8 | | libc |
(4 rows)
katalog=# \c katalog
You are now connected to database "katalog" as user "katalog".
katalog=# CREATE TABLE produk (nama TEXT, harga INTEGER);
CREATE TABLE
katalog=# INSERT INTO produk VALUES ('Kopi Torabika', 15000);
INSERT 0 1
katalog=# SELECT * FROM produk;
nama | harga
---------------+-------
Kopi Torabika | 15000
(1 row)
katalog=# \qPrompt katalog=# di setiap baris adalah prompt psql beserta ketikan kita; baris-baris di antaranya adalah jawaban server. Mari kita telaah perintah-perintah meta dan SQL di atas:
\l— perintah meta psql yang mendaftar seluruh database;kataloghadir berkatPOSTGRES_DB, ditemanipostgres,template0, dantemplate1yang selalu lahir pada setiap cluster. Tanda#pada prompt menandakan kita terhubung sebagai superuser — memang user katalog adalah superuser hasil inisialisasi.\c katalog— cara resmi berpindah database; perintah ini tetap sah meski kita memang sudah berada di katalog, dan psql membalasnya dengan konfirmasi koneksi ulang. Perintah meta selalu diawali garis miring dan tidak diakhiri titik koma.CREATE TABLE produk (nama TEXT, harga INTEGER)— lahirkan tabel dengan dua kolom; jawabanCREATE TABLEmenegaskan keberhasilan tanpa basa-basi.INSERT 0 1— angka terakhir adalah jumlah baris yang masuk: satu barisKopi Torabikaseharga 15000 kini tercatat.SELECT * FROM produk— baca kembali seluruh isi tabel; satu baris yang sama tampil rapi dalam bingkai ASCII khas psql.\q— tutup sesi psql; proses tamu ini berakhir, sementara containerdbbeserta server PostgreSQL di dalamnya tetap berjalan.
Tabel produk dengan satu baris Kopi Torabika ini sengaja kita biarkan hidup di volume db-data — ia akan menjadi bukti ketika Chapter Persistensi Database mengadu seluruh isi section ini melawan docker rm -f.
Inisialisasi Hanya Terjadi Sekali #
Ada satu perilaku entrypoint postgres yang wajib dipahami sejak awal, karena paling sering mengejutkan pendatang baru:
Entry point image memeriksa isi direktori data sebelum bekerja: kosong berarti menjalankan initdb dan memproses variabel; berisi cluster berarti langsung menyalakan server. Direktori yang dimaksud dikendalikan oleh variabel PGDATA yang dibawa image — kita bisa membaca nilainya langsung dari environment container:
$ docker exec db printenv PGDATAOutput yang Dihasilkan:
/var/lib/postgresql/dataNilai itulah yang kita pasangi volume db-data pada perintah pertama — kebetulan yang sempurna antara alamat data resmi image dan titik pemasangan volume kita. Variabel PGDATA juga menjadi tuas resmi bila suatu saat kita ingin memindahkan lokasi data ke path lain, dan berkas penanda PG_VERSION di dalam direktori itu mencatat angka versi mayor cluster — angka yang kelak menjadi peta ketika kita membahas cara mengangkat data lama ke image versi mayor baru pada Chapter Migrasi Versi Database.
Tanpa -p dan Pola Aman Port #
Perhatikan bahwa perintah kita tidak memuat flag -p sama sekali — dan itu bukan kelalaian. Database ini hanya perlu dijangkau oleh container aplikasi lewat network bersama, pola yang kita bangun sepanjang Section Jaringan Container dan Section Docker Compose. Menerbitkan port database ke seluruh antarmuka host justru membuka permukaan serangan tanpa keuntungan apa pun di mesin development. Kita bisa membuktikan statusnya lewat docker port:
$ docker port dbOutput yang Dihasilkan:
Kekosongan output itu adalah jawabannya: tidak ada satu pun port yang diterbitkan ke host. Bila suatu saat alat di host memang harus tersambung — GUI client misalnya — terbitkan port hanya pada antarmuka loopback:
$ docker run -d --name db -p 127.0.0.1:5432:5432 ... postgres:18-alpineBentuk 127.0.0.1:5432:5432 membatasi pintu ke localhost sehingga mesin lain di jaringan tidak bisa mengetuknya — mekanisme lengkapnya sudah kita bedah pada Chapter Port Publishing.
pg_isready sebagai Sinyal Kesiapan #
Kapan tepatnya container db siap menerima klien? Log sudah menjawab dengan database system is ready to accept connections, tetapi ada alat yang lebih ringkas untuk skrip dan otomatisasi:
$ docker exec db pg_isready -U katalog -d katalogOutput yang Dihasilkan:
/var/run/postgresql:5432 - accepting connectionspg_isready mengetuk server lewat socket dan melaporkan statusnya tanpa membuka sesi penuh; exit code 0 berarti siap, sehingga perintah ini menjadi bahan healthcheck paling jujur untuk PostgreSQL. Kita sudah memakainya sebagai probe gerbang service_healthy pada Chapter Healthcheck dan Service Readiness — dan saat pola-pola chapter ini pindah ke compose.yaml, itulah tepatnya tempatnya kembali.
PostgreSQL kini berjalan, berisi tabel produk yang terisi, dan datanya menumpang volume bernama. Namun PostgreSQL hanyalah satu dari banyak mesin database yang menghuni server produksi dunia nyata — tetangga paling populernya menyandang nama yang bahkan lebih tua: MySQL. Chapter Menjalankan MySQL mengulang ritual yang sama pada image mysql:8.4, membedah keluarga variabel MYSQL_* yang punya aturan wajib-opsional berbeda, lalu menyapa servernya lewat klien mysql.
Referensi #
- PostgreSQL — Docker Hub — daftar variabel POSTGRES_*, perilaku initdb, dan catatan PGDATA pada image resmi.
- docker container run — CLI Reference
— flag
-e,-v,-d, dan bentuk-pdengan alamat antarmuka. - Volumes — mekanisme volume bernama yang menampung cluster PostgreSQL.
- PostgreSQL Documentation — rujukan klien psql, perintah meta, dan utilitas pg_isready.