Proyek-proyek sebelumnya selalu membuka dengan berkas sumber aplikasi, lalu Dockerfile yang mengemasnya. Kali ini urutannya terbalik dan lebih pendek: tidak ada aplikasi yang kita tulis dan tidak ada resep yang kita bangun — hanya satu berkas compose.yaml berisi 43 baris yang merangkai dua image resmi menjadi satu stack utuh. Seluruh keputusan arsitektur hidup di berkas itu: image versi berapa, variabel apa yang disuntikkan, data apa yang dipersist-kan, dan service mana yang boleh menyala lebih dulu. Kita bedah berkas itu baris demi baris, mulai dari sisi WordPress, karena sisi itulah yang terlihat pertama kali oleh pengunjung situs.
compose.yaml untuk Dua Service #
Buat berkas compose.yaml di akar folder proyek dengan isi berikut — persis seperti ini, karena kita akan membedahnya sepotong-sepotong pada bagian-bagian selanjutnya:
# WordPress + MySQL: dua service jadi, dua volume, tanpa Dockerfile.
# Nama proyek Compose otomatis diambil dari nama folder karena berkas
# ini sengaja tidak memakai kunci `name:`.
services:
db:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD wajib diisi lewat .env}"
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: "${MYSQL_PASSWORD:?MYSQL_PASSWORD wajib diisi lewat .env}"
volumes:
- db-data:/var/lib/mysql
healthcheck:
# $$ membuat dolar lolos dari interpolasi Compose; shell di dalam
# container yang mengekspansi $MYSQL_ROOT_PASSWORD saat test berjalan.
test: ["CMD-SHELL", "mysqladmin ping -h localhost -p$$MYSQL_ROOT_PASSWORD"]
interval: 3s
timeout: 3s
retries: 20
start_period: 30s
wordpress:
image: wordpress:6.8-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8090:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: "${MYSQL_PASSWORD:?MYSQL_PASSWORD wajib diisi lewat .env}"
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html/wp-content
volumes:
db-data:
wp-content:Bentuk besarnya akrab dari Struktur compose.yaml: satu blok services berisi dua service (db dan wordpress), satu blok volumes yang mendeklarasikan dua volume bernama, dan tidak ada satu kunci build pun — Compose cukup menarik kedua image dari registry. Mari kita telaah enam kunci pada service wordpress:
image: wordpress:6.8-apache— image resmi WordPress versi 6.8, dipin pada tag eksplisit alih-alihlatest— kebiasaan yang kita tanam sejak Memahami Image Tag. Suffix-apachememilih varian yang menyatukan Apache dan mod_php dalam satu image; itulah varian paling cocok untuk proyek tanpa Dockerfile, karena satu container sudah melayani HTTP tanpa perlu service web terpisah seperti pada varian-fpm.environmentdengan empatWORDPRESS_DB_*— alamat, user, sandi, dan nama database yang dipakai WordPress. Empat variabel ini adalah jembatan resmi kewp-config.php; mekanisme persisnya kita bedah pada bagian berikutnya. Perhatikan bahwaWORDPRESS_DB_PASSWORDdiambil dari interpolasi${MYSQL_PASSWORD:?…}— variabel yang sama dengan sisidb, sehingga kedua service tidak mungkin salah nyambung.volumes: wp-content— satu-satunya path WordPress yang dipersist-kan:/var/www/html/wp-content, rumah tema, plugin, dan unggahan. Berkas inti WordPress (wp-admin,wp-includes, seluruh core) sengaja tidak dipersist-kan karena ia milik image — wujud pemisahan program dan data yang kita bahas pada Konsep Persistence.ports: "8090:80"— menerbitkan port host 8090 menuju port 80 container. Angka 8090 dipilih karena 8080 sering sudah ditempati stack latihan lain di mesin yang sama. Tanda kutipnya bukan hiasan — peringatannya kita pasang tepat di bawah ini.depends_ondengancondition: service_healthy— WordPress tidak menyala begitudbdinyalakan, melainkan menunggu hinggadblulus healthcheck. Inilah gerbang kesiapan yang kita kenal dari depends_on dan Startup Order: urutan start yang sebenarnya, bukan sekadar urutan eksekusi.restart: unless-stopped— kebijakan yang sama dengan proyek sebelumnya: container bangkit otomatis saat daemon atau host menyala ulang, tetapi patuh berhenti bila kita yang menghentikannya secara sengaja — nuansa yang kita bedah pada Restart Policy.
Satu keputusan yang kelihatan seperti kekurangan justru disengaja: service db tidak memiliki kunci ports sama sekali. MySQL hanya hidup di dalam network project yang dibuat Compose, dan satu-satunya jalur menujunya adalah nama service — WordPress menghubungi db:3306 lewat DNS embedded yang bekerja pada user-defined network, sebagaimana kita buktikan pada Container DNS. Dari luar host, port 3306 tidak diterbitkan apa pun: database tidak bisa disambung oleh internet atau bahkan oleh proses host sembarangan. Mengurangi permukaan yang terbuka sejak dari desain adalah bentuk paling murah dari Prinsip Keamanan Container.
Jembatan WORDPRESS_DB_* menuju wp-config.php #
Instalasi WordPress klasik dimulai dengan mengedit wp-config.php secara manual — mengisi DB_HOST, DB_USER, DB_PASSWORD, DB_NAME dengan tangan. Image resmi WordPress memotong ritual itu lewat entrypoint-nya. Setiap kali container menyala, skrip entrypoint melakukan dua pekerjaan berurutan: menyalin isi /usr/src/wordpress ke /var/www/html (isi wp-content yang sudah ada dipertahankan, tidak ditimpa), lalu — bila wp-config.php belum ada — menyalin template wp-config-docker.php menjadi wp-config.php. Template itu tidak memuat sandi apa pun secara tetap; ia membaca keempat variabel WORDPRESS_DB_* dari environment container lewat helper getenv_docker() — pasangan variabel dan nilai cadangannya — lalu memetakannya ke konstanta yang dibaca WordPress:
| Variabel environment | Konstanta di wp-config.php | Nilai pada stack kita |
|---|---|---|
WORDPRESS_DB_HOST | DB_HOST | db:3306 |
WORDPRESS_DB_USER | DB_USER | wordpress |
WORDPRESS_DB_PASSWORD | DB_PASSWORD | nilai MYSQL_PASSWORD dari .env |
WORDPRESS_DB_NAME | DB_NAME | wordpress |
Akibat pemetaan ini terasa begitu kita membuka wizard: halaman pengisian koneksi database yang biasa menjadi langkah pertama instalasi tidak pernah muncul, karena wp-config.php sudah ada dan sudah terisi. Konfigurasi database berpindah dari ritual manual di dalam container menjadi deklarasi di compose.yaml — persis filosofi yang kita bangun sejak Pengantar Docker Compose.
Kredensial dalam .env.example #
Sekarang bagian yang belum kita miliki: nilai dua sandi. Berkas compose.yaml tidak memuatnya — ia hanya meminta variabel MYSQL_ROOT_PASSWORD dan MYSQL_PASSWORD dengan gerbang :?, dan penyedia nilainya adalah berkas .env di root proyek. Template-nya disimpan sebagai .env.example:
# Salin berkas ini menjadi .env lalu ganti kedua nilainya:
# cp .env.example .env
# Berkas .env berisi kredensial dan tidak boleh di-commit (lihat .gitignore).
# Password akun root MySQL.
MYSQL_ROOT_PASSWORD=ganti-root-password-ini
# Password user aplikasi "wordpress" yang dibuat otomatis oleh
# entrypoint MySQL saat volume db-data masih kosong.
MYSQL_PASSWORD=ganti-password-wordpress-iniHanya dua variabel yang diminta — bukan empat — karena MYSQL_DATABASE dan MYSQL_USER tidaklah rahasia: nilainya wordpress tercetak polos di compose.yaml. Sandi adalah satu-satunya bagian yang berpindah ke .env, sesuai prinsip satu berkas per rahasia yang kita rinci di Environment dan env File. Gerbang pada nilainya dibangun oleh pola ${MYSQL_PASSWORD:?pesan} yang kita bedah pada Variable Interpolation: tanda :? berarti “bila variabel kosong atau tidak ada, gagalkan perintah dan cetak pesan ini”. Gerbang itu bisa kita saksikan menembak kapan saja setelah .env tersalin — cukup sembunyikan sementara berkasnya lalu minta Compose merender konfigurasi:
$ mv .env .env.simpan
$ docker compose configOutput yang Dihasilkan:
required variable MYSQL_ROOT_PASSWORD is missing a value: MYSQL_ROOT_PASSWORD wajib diisi lewat .env$ mv .env.simpan .envPesan galatnya memuat dua hal yang kita tulis sendiri: nama variabel dan kalimat petunjuknya. Gerbang kembar pada MYSQL_PASSWORD menembak dengan pesan yang serupa bila salah satu saja kosong. Gagal di sini berarti gagal sebelum satu container pun tersentuh — jauh lebih baik daripada stack yang menyala dengan sandi kosong lalu mati lima menit kemudian dengan galat koneksi yang menjauh dari akar masalah. Karena berisi kredensial, .env tidak boleh masuk repositori — tugas yang dipegang .gitignore:
# Berkas .env berisi kredensial — jangan pernah di-commit.
.envMenyalakan Stack #
Ritual penyalaan cuma dua langkah: salin template menjadi .env, ganti kedua nilainya dengan sandi pilihan kita, lalu nyalakan stack. Pastikan tidak ada stack lain yang sedang menempati port 8090 di host — bila sisa latihan masih menyala, bongkar dulu dengan docker compose down dari foldernya:
$ cp .env.example .env
$ docker compose up -dOutput yang Dihasilkan:
[+] Pulling 2/2
✔ db Pulled 21.6s
✔ wordpress Pulled 13.1s
[+] Running 3/3
✔ Network wordpress-dan-mysql_default Created 0.2s
✔ Container wordpress-dan-mysql-db-1 Healthy 11.4s
✔ Container wordpress-dan-mysql-wordpress-1 Started 1.9sMari kita telaah enam baris kemajuan di atas:
[+] Pulling 2/2— kedua image resmi ditarik pada pemakaian pertama; keduanya berukuran ratusan megabyte, dengandbpaling lama karena image MySQL membawa server lengkap di atas basisnya. Di mesin yang sudah pernah menarik salah satunya — misalnya dari Chapter Menjalankan MySQL — Compose hanya menarik yang hilang, sesuai kebijakanmissingbawaan yang kita kenal dari Image, Build, dan Pull.Network … Created— networkwordpress-dan-mysql_defaulttercipta otomatis dengan nama folder sebagai prefiks; di sinilah namadbmenjadi alamat DNS yang sah.db … Healthydalam 11,4 detik — angka paling penting: volume kosong memaksa entrypoint MySQL melakukan inisialisasi penuh (tabel sistem, databasewordpress, user aplikasi), dan Compose menunggu hingga probemysqladmin pinglulus sebelum melanjutkan.wordpress … Startedsetelahnya — baris ini hanya tercipta karena gerbangservice_healthyterbuka; tanpa healthcheck, WordPress akan menyala bersamaan dengandbdan kehilangan beberapa detik pertamanya untuk galat koneksi.
Status akhirnya kita baca dalam satu pandangan lewat docker compose ps:
$ docker compose psOutput yang Dihasilkan:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
wordpress-dan-mysql-db-1 mysql:8.4 "docker-entrypoint.s…" db 3 minutes ago Up 3 minutes (healthy) 3306/tcp
wordpress-dan-mysql-wordpress-1 wordpress:6.8-apache "docker-entrypoint.s…" wordpress 3 minutes ago Up 3 minutes 0.0.0.0:8090->80/tcpDua baris ini menegaskan dua keputusan desain kita. Pertama, (healthy) pada db — probe masih berdenyut setiap 3 detik dan gerbangnya tetap terjaga. Kedua, kolom PORTS: WordPress menerbitkan 0.0.0.0:8090->80/tcp, sedangkan db hanya menampilkan 3306/tcp — port tersebut terekspose oleh image tetapi tidak dipublikasikan; tidak ada pemetaan ke host, sehingga satu-satunya pintu masuk ke MySQL adalah lewat network internal.
Wizard Instalasi dalam Empat Langkah #
Buka browser ke localhost:8090 dan wizard instalasi menyambut dalam empat langkah pendek:
- Pilih bahasa — daftar bahasa WordPress lengkap hadir; pilih Bahasa Indonesia lalu klik Lanjut.
- Identitas situs — isi judul situs
Toko Kopi Kita, username adminbudi, password kuat (WordPress menawarkan generatornya), dan alamat email semisal[email protected]. - Klik Install WordPress — beberapa detik kemudian muncul halaman sukses WordPress telah sukses terinstal.
- Klik Log In — masuk dengan akun
budidan dashboardwp-adminterbuka lengkap.
Langkah yang tidak muncul adalah halaman koneksi database — biasanya wizard klasik meminta nama database, user, sandi, dan host di layar pertama. Di stack kita layar itu dilewati total karena wp-config.php sudah ditulis entrypoint dari keempat WORDPRESS_DB_*; database sudah terisi koneksi yang benar sejak container pertama menyala. Dalam waktu kurang dari satu menit setelah docker compose up -d, situs Toko Kopi Kita sudah hidup penuh dengan akun admin miliknya.
Stack yang barusan kita lahirkan ini masih menyimpan pertanyaan yang belum terjawab: apa sebenarnya pekerjaan masing-masing variabel MYSQL_*, mengapa healthcheck memerlukan dua tanda dolar, dan mengapa akun aplikasi berbeda dari root. Bagian 2: Mengonfigurasi Database menjawab ketiganya — lengkap dengan pembuktian least privilege langsung dari dalam container database.
Referensi #
- WordPress — Docker Hub
— variabel
WORDPRESS_DB_*, mekanismewp-config-docker.php, dan daftar tag varian image. - Environment variables — Compose
— berkas
.env, prioritas sumber, dan sintaks interpolasi${VAR:?pesan}.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
compose.yaml
Unduh berkas# WordPress + MySQL: dua service jadi, dua volume, tanpa Dockerfile.
# Nama proyek Compose otomatis diambil dari nama folder karena berkas
# ini sengaja tidak memakai kunci `name:`.
services:
db:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD wajib diisi lewat .env}"
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: "${MYSQL_PASSWORD:?MYSQL_PASSWORD wajib diisi lewat .env}"
volumes:
- db-data:/var/lib/mysql
healthcheck:
# $$ membuat dolar lolos dari interpolasi Compose; shell di dalam
# container yang mengekspansi $MYSQL_ROOT_PASSWORD saat test berjalan.
test: ["CMD-SHELL", "mysqladmin ping -h localhost -p$$MYSQL_ROOT_PASSWORD"]
interval: 3s
timeout: 3s
retries: 20
start_period: 30s
wordpress:
image: wordpress:6.8-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8090:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: "${MYSQL_PASSWORD:?MYSQL_PASSWORD wajib diisi lewat .env}"
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html/wp-content
volumes:
db-data:
wp-content: