PostgreSQL kini hidup di container bernama db dengan tabel produk yang terisi, dan ritualnya ternyata ringkas: sunting environment, pasang volume, baca log, lalu sapa lewat klien. Pertanyaan alami berikutnya adalah seberapa jauh ritual itu bisa dipindahkan ke mesin database lain — dan kandidat paling layak untuk ujian pertama adalah yang paling banyak dipakai di dunia: MySQL. Image resminya menempuh filosofi yang sama dengan postgres, tetapi keluarga variabel MYSQL_* punya aturan main sendiri: satu variabel wajib, tiga lainnya hanya bekerja sebagai inisialisasi, dan servernya menyala lewat tarian dua babak yang tercatat jelas di log. Chapter ini menjalankan MySQL 8.4 berdampingan dengan PostgreSQL kita, tanpa satu paket host pun terpasang.

Menjalankan MySQL dengan Empat Variabel #

Perintah berikut menyalakan MySQL 8.4 sebagai container db-mysql, dengan identitas yang kita kenali dari proyek katalog:

bash
$ docker run -d --name db-mysql \
    -e MYSQL_ROOT_PASSWORD=rahasia123 \
    -e MYSQL_DATABASE=katalog \
    -e MYSQL_USER=katalog \
    -e MYSQL_PASSWORD=rahasia123 \
    -v db-data-mysql:/var/lib/mysql \
    mysql:8.4

Output yang Dihasilkan:

text
bcf53b80b3c48dd510df3831043c21df1910a208d005e8dc5cd60f0bb72d7cc3

Image mysql:8.4 sudah ada di lokal sejak Chapter Permission dan Ownership Volume, jadi daemon langsung melahirkan container tanpa menarik apa pun. Mari kita telaah enam bagian perintah ini:

  1. -d --name db-mysql — detached dengan nama db-mysql; nama yang berbeda dari db membuat kedua database hidup berdampingan di mesin yang sama tanpa saling menabrak.
  2. -e MYSQL_ROOT_PASSWORD=rahasia123 — satu-satunya variabel yang benar-benar wajib: entrypoint MySQL menolak bekerja tanpa kata sandi root. Dua jalan pintas sah ada — MYSQL_ALLOW_EMPTY_PASSWORD untuk latihan santai dan MYSQL_RANDOM_ROOT_PASSWORD yang menghasilkan sandi acak di log — tetapi keduanya jarang tepat untuk kebiasaan baik.
  3. -e MYSQL_DATABASE=katalog — database awal yang dibuat saat inisialisasi; padanan langsung POSTGRES_DB.
  4. -e MYSQL_USER=katalog dan MYSQL_PASSWORD — user terbatas yang diciptakan sekaligus, lengkap dengan sandinya; user ini otomatis diberi hak penuh atas MYSQL_DATABASE, sehingga aplikasi tidak perlu menyentuh root sama sekali.
  5. -v db-data-mysql:/var/lib/mysql — volume bernama sendiri pada direktori data resmi MySQL; perhatikan bahwa path-nya memang /var/lib/mysql, bukan path postgres — dan volumenya juga kita beri nama berbeda supaya tidak tertukar.
  6. mysql:8.4 — seri LTS 8.4, tag yang dipin eksplisit alih-alih latest.

Ketiga variabel opsional itu bekerja hanya sebagai inisialisasi: sama seperti postgres, entrypoint MySQL memprosesnya saat direktori data masih kosong dan mengabaikannya begitu volume berisi. Aturan wajib-opsionalnya dirangkum pada tabel berikut:

VariabelStatusPeran
MYSQL_ROOT_PASSWORDwajib — salah satu dari tiga opsi sandi rootkata sandi user root saat inisialisasi
MYSQL_DATABASEopsionaldatabase awal yang dibuat saat inisialisasi
MYSQL_USERopsionaluser aplikasi yang dibuat bersama database awal
MYSQL_PASSWORDopsionalkata sandi untuk MYSQL_USER

Log Startup yang Dua Kali Berkata Siap #

MySQL membutuhkan waktu lebih lama untuk penyalaan pertama — belasan detik untuk menyiapkan berkas data — dan lognya menceritakan tarian dua babak yang menarik:

bash
$ docker logs db-mysql

Output yang Dihasilkan:

text
2026-10-13 20:52:02+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server started.
2026-10-13 20:52:02+00:00 [Note] [Entrypoint]: Initializing database files
2026-10-13 20:52:08.617293Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.4.11) initializing of database in progress...
...
2026-10-13 20:52:24+00:00 [Note] [Entrypoint]: Database files initialized
2026-10-13 20:52:24+00:00 [Note] [Entrypoint]: Starting temporary server
2026-10-13 20:52:27+00:00 [Note] [Entrypoint]: Temporary server started.
2026-10-13 20:52:27.812315Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.11'  socket: '/var/run/mysqld/mysqld.sock'  port: 0
2026-10-13 20:52:27+00:00 [Note] [Entrypoint]: Creating database katalog
2026-10-13 20:52:27+00:00 [Note] [Entrypoint]: Creating user katalog@'%'
2026-10-13 20:52:27+00:00 [Note] [Entrypoint]: Giving user katalog@'%' access to schema katalog
...
2026-10-13 20:52:31+00:00 [Note] [Entrypoint]: Temporary server stopped.
2026-10-13 20:52:31+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up.
...
2026-10-13T20:52:33.104522Z 0 [System] [MY-015015] [Server] MySQL Server Initialization - start.
2026-10-13T20:52:34.512844Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.11'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  mysqlx socket: '/var/run/mysqld/mysqlx.sock'  mysqlx port: 33060.

Baris-baris menengah dipotong agar ringkas. Mari kita telaah alurnya:

  1. Initializing database files — babak pertama: entrypoint menyiapkan direktori data dari nol di dalam volume; inilah pekerjaan yang membuat penyalaan pertama terasa lambat.
  2. Starting temporary server — server sementara dinyalakan bukan untuk melayani klien, melainkan agar entrypoint bisa terhubung sebagai klien dan menjalankan tugas administratif — tiga baris Creating database katalog, Creating user katalog@'%', hingga pemberian hak aksesnya adalah hasil kerja server sementara ini membaca variabel MYSQL_* kita.
  3. Temporary server stopped dan Ready for start up — babak pertama selesai; berkas-berkas sudah siap dan server sungguhan dinyalakan sebagai proses utama container.
  4. ready for connections muncul dua kali — yang pertama, dengan port: 0, adalah milik server sementara yang hanya mendengarkan socket lokal; yang terakhir milik server sungguhan: MySQL siap menerima koneksi, versinya 8.4.11 persis seperti seri yang dijanjikan tag mysql:8.4, socket lokalnya hidup di /var/run/mysqld/mysqld.sock, dan port TCP-nya 3306 — tetap di dalam container, karena kita sekali lagi tidak memakai -p.
  5. mysqlx port 33060 — MySQL 8 juga membuka protokol X DevAPI tambahan; cukup diketahui ada, kita tidak memakainya di course ini.

Rentang antara kedua kalimat siap itulah jendela rapuhnya: container sudah tampak hidup di docker ps, padahal server sungguhan belum membuka port. Aplikasi Budi dulu gagal terhubung tepat di jendela seperti ini pada stack Compose — dan itulah alasan Chapter depends_on dan Startup Order membutuhkan healthcheck sebelum menyalakan service yang bergantung.

Sesi mysql sebagai User katalog #

Klien resmi image ini adalah mysql, dan pola docker exec -it membawanya ke terminal kita:

bash
$ docker exec -it db-mysql mysql -ukatalog -prahasia123

Output yang Dihasilkan:

text
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 8.4.11 MySQL Community Server - GPL

Copyright (c) 2000, 2026, Oracle and/or its affiliates.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| katalog            |
+--------------------+
2 rows in set (0.00 sec)

mysql> USE katalog;
Database changed
mysql> SHOW TABLES;
Empty set (0.00 sec)

mysql> CREATE TABLE produk (nama VARCHAR(100), harga INT);
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO produk VALUES ('Kopi Torabika', 15000);
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM produk;
+---------------+-------+
| nama          | harga |
+---------------+-------+
| Kopi Torabika | 15000 |
+---------------+-------+
1 row in set (0.00 sec)

mysql> exit
Bye

Baris peringatan di awal dan banner selamat datang adalah ucapan pembuka klien mysql; seluruh sesi berjalan dalam satu blok karena docker exec -it menautkan terminal kita langsung ke proses klien. Mari kita telaah isinya:

  1. -ukatalog -prahasia123 — klien menerima user dan sandi lewat argumen rapat tanpa spasi; peringatan yang menyertainya jujur — sandi pada baris perintah terbaca siapa pun yang bisa membaca daftar proses. Untuk latihan ini dapat ditoleransi, dan pola --env-file dari Chapter Environment Variable menjadi jalan yang lebih tertata begitu daftar variabel memanjang.
  2. SHOW DATABASES hanya menampilkan dua — kita masuk sebagai user katalog, dan MySQL menyembunyikan database yang bukan hak kita; information_schema dan katalog saja yang terlihat. Bandingkan dengan sesi root yang akan menampilkan mysql, performance_schema, dan sys — tabel-tabel internal milik server.
  3. USE katalog — padanan \c di psql; perintah ini memilih database kerja tanpa membuka koneksi baru, dijawab singkat Database changed.
  4. SHOW TABLES mengembalikan Empty set — database katalog baru saja lahir dan masih kosong; Empty set adalah bahasa MySQL untuk “nol baris”.
  5. CREATE TABLE dan INSERT — skema yang sama dengan versi postgres-nya, hanya idiom tipenya menyesuaikan: VARCHAR(100) dan INT; jawaban Query OK diikuti jumlah baris yang terpengaruh.
  6. exit — menutup sesi klien; container dan servernya tetap hidup, persis perilaku docker exec yang kita kenal.

Catatan Autentikasi dan Karakter Set #

Dua perilaku bawaan MySQL 8 pantas dicatat sebelum kita lanjut. Pertama soal autentikasi: sejak seri 8, user baru lahir dengan plugin caching_sha2_password, dan di 8.4 plugin lama mysql_native_password sudah tidak aktif — klien lawas yang hanya mengerti plugin lama akan menolak terhubung sampai klien itu dimutakhirkan. Kebanyakan driver modern sudah lama kompatibel, jadi catatan ini biasanya baru terasa saat menyambungkan tool peninggalan.

Kedua soal karakter: seluruh database baru di MySQL 8 otomatis memakai utf8mb4 — UTF-8 sungguhan yang menampung hingga empat byte per karakter, sehingga emoji dan aksara Nusantara tersimpan tanpa kecelakaan. Nama Kopi Torabika kita memang polos, tetapi kolom yang sama akan sama tenang menampung nama produk seaneh apa pun yang dirancang tim pemasaran.

mysqladmin ping sebagai Healthcheck #

MySQL punya padanan pg_isready yang lebih ringkas dari sesi penuh — alat administrasi mysqladmin dengan subperintah ping:

bash
$ docker exec db-mysql mysqladmin ping -ukatalog -prahasia123

Output yang Dihasilkan:

text
mysql: [Warning] Using a password on the command line interface can be insecure.
mysqld is alive

Satu kalimat mysqld is alive adalah seluruh laporannya: server menjawab ketukan dan siap bekerja. Exit code 0 menjadikannya bahan healthcheck yang jujur untuk skrip maupun untuk kunci healthcheck di compose.yaml — peran yang sudah kita praktikkan untuk postgres pada Chapter Healthcheck dan Service Readiness dan akan berulang setiap kali stack butuh tahu bahwa databasenya benar-benar bangun.

Peta Menuju MariaDB #

Sebelum melangkah, mari kita pasang peta kecil untuk chapter berikutnya. MariaDB adalah fork MySQL yang kompatibel protokol, dan image resminya menerima variabel MYSQL_* yang barusan kita pakai — tetapi ia lebih suka dipanggil dengan namanya sendiri:

Di MySQLPadanan di MariaDBPeran
MYSQL_ROOT_PASSWORDMARIADB_ROOT_PASSWORDkata sandi root saat inisialisasi
MYSQL_DATABASEMARIADB_DATABASEdatabase awal
MYSQL_USERMARIADB_USERuser aplikasi
MYSQL_PASSWORDMARIADB_PASSWORDkata sandi user aplikasi

Mengapa ada dua nama untuk hal yang sama, kapankah MariaDB menjadi pilihan yang lebih tepat daripada MySQL, dan di mana letak jebakan yang membuat keduanya tidak bisa saling menukar volume — itulah agendanya Chapter Menjalankan MariaDB.

MySQL 8.4 kini berdiri di samping PostgreSQL: dua server, dua volume, satu mesin, nol paket host. Namun cerita keluarga MySQL belum selesai — pada 2010-an sebagian komunitasnya berpisah dan membangun rumah sendiri bernama MariaDB, dan rumah itu kini menjadi default database di banyak distribusi Linux. Chapter Menjalankan MariaDB membawa kita ke sana: image yang hampir identik, variabel yang hampir sama, dan satu-dua kejutan yang hanya muncul ketika dua dunia ini dipaksa berbagi barang.

Referensi #