Akar Permasalahan
Salah satu server yang kami manage udah terlalu tua. Ubuntu, versi 18.04. Sudah EOL. Dan, sudah tidak lagi disupport oleh ppa:ondrej/php.
Hasilnya?
Tidak bisa install PHP, terlebih versi terbaru yang memang pernah terinstall sebelumnya.
Bukan cuman itu, semua ekstensi php juga tidak bisa diinstall.
Solusinya? Ada dua. Eh, tiga.
- Upgrade OS (setup semuanya lagi dari awal)
- Downgrade projek agar pakai PHP lawas (bukan solusi)
- Compile PHP sendiri (sudah dicoba, berhasil untuk base php. Tapi terlalu banyak ekstensi yang dibutuhkan yang perlu install banyak dependencies lain dari ubuntu yang juga udah gak support).
- Pakai docker.
Eh, ada 4 ya?
Ya begitu, pada akhirnya kami memutuskan untuk pakai: docker (docker lagi! docker lagi!).
Pakai Docker Tapi…
Karena ini server untuk development, jadi ada banyak projek, dan juga ada banyak stack lain di samping PHP seperti: MySQL, redis, node, Postgres, dll. Yang mana itu semua membuat migrasi ke docker menjadi ribet, apalagi kalau dilakukan dalam scope per projek.
Satu projek satu docker compose, ada image buat nginx-nya sendiri, ada image buat mysql/postgres, untuk redis, dll. Belum lagi migrasi data dari OS host ke image. Ribet.
Akhirnya kami coba eksperimen: bagaimana kalau kita cuman migrasi ke docker untuk PHP-nya aja? Dan semua-muanya tetap pakai native instalasi di OS host?!
Konsep Normal Nginx + PHP FPM (via file socket)
Konfigurasi NORMAL untuk Nginx + php8.1-fpm (contoh sangat minimalis):
server {
# pass PHP scripts to FastCGI server
location ~ \index.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
}
}
Untuk versi php lainnya sama saja:
server {
# pass PHP scripts to FastCGI server
location ~ \index.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
}
}
Nginx (host) + PHP FPM (Docker) via TCP
Sedangkan untuk konsep komunikasi antar Nginx di Host dengan PHP fpm yang jalan di docker adalah dengan menggunakan TCP, bukan menggunakan file socket (.sock).
Jadi, kita run php-fpm di dalam docker untuk me-listen port tertentu. Kemudian kita arahkan nginx menuju port tersebut.
Sehingga konfigurasinya menjadi seperti ini:
server {
# pass PHP scripts to FastCGI server
location ~ \index.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9082;
}
}
Untuk versi php lain, bisa kita bedakan portnya (default bawaan php-fpm adalah listen ke port 9000):
server {
# pass PHP scripts to FastCGI server
location ~ \index.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9084;
}
}
Sekarang, nginx akan meneruskan instruksi php ke ip dan port yang sudah didefinisikan, melalui jaringan TCP/IP. Bukan lagi menggunakan jalan pintas file .sock.
Wujud Penampakan Dockerfile
Sebelum kita mulai, di sini saya akan membuat dua file di dalam satu folder (bebas), filenya terlihat seperti berikut (abaikan aja readme):
.
├── Dockerfile
├── README
└── docker-compose.yml
Idealnya kita tetap perlu menggunakan custom image alias custom Dockerfile. Kenapa gak pakai yang default? Karena kita tetap perlu install ekstensi-ekstensi php yang gak ada di image default.
Image default yang kita pakai adalah image official php: php:8.2-fpm.
Dan begini kira-kira Dockerfile yang akan kita build:
FROM php:8.2-fpm
# Install dependencies and the pdo_mysql extension
RUN apt-get update && \
apt-get install -y \
libzip-dev \
libonig-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev && \
# Install GD extension (and enable support for commonly used formats)
docker-php-ext-configure gd --with-freetype --with-jpeg && \
# Install required PHP extensions
docker-php-ext-install pdo_mysql mysqli zip opcache gd
Di sini bisa teman-teman perhatikan bahwa kita masih perlu install beberapa package ubuntu (debian), dan juga masih perlu install ekstensi tambahan sesuai dengan keperluan projek.
Wujud Penampakan docker-compose.yml
Berikutnya setelah Dockerfile ready, kita bikin file docker-compose.yml.
Saya suka sama docker compose karena manage-nya lebih gampang dari pada kalau pakai docker biasa. Perintah yang kita eksekusi jadi lebih singkat karena settingan-nya udah kita definiskan dalam file yml.
version: '3.8'
services:
php:
build:
context: .
dockerfile: Dockerfile
image: php-ku:8.2-fpm
container_name: php82-fpm
volumes:
- /var/www:/var/www
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "127.0.0.1:9082:9000"
user: "${UID:-1000}:${GID:-1000}"
Beberapa hal yang paling penting di sini:
volumesextra_hostsportsuser
Kok penting semua? Iya!
Volumes
Volumes berarti kita memberi akses / me-link-kan /var/www di mesin host ke dalam /var/www yang ada di dalam container.
Dan perlu diketahui kalau path projek yang ada di host, harus sama dengan path projek yang ada di dalam kontainer.
Dalam artian: jika kita punya projek di direktori /home/fufu/fafa/project maka direktori tersebut juga harus ada di dalam kontainer, kita tinggal tambahkan di volumes.
Extra hosts
Extra hosts penting karena nantinya kita bisa akses host.docker.internal sebagai cara hubung aplikasi di dalam container ke mesin host. Contoh ketika projek php kita mau konek ke mysql, yang host nya bukan di dalam container, kita bisa pakai host host.docker.internal. Kalau di laravel, env nya jadi begini:
DB_HOST=host.docker.internal
Ports
Sedangkan ports adalah cara kita mengekspose port yang ada di dalam container, ke port yang bisa diakses oleh mesin host.
User
Ini bisa penting bisa tidak, tergantung dengan konfigurasi server. Intinya adalah untuk mensinkronkan hak akses user yang ada di mesin host (pemilik asli /var/www) dan dengan user yang berada di dalam container. Tujuannya biar tidak ada permasalahan permission denied ketika berhubungan dengan file io.
Jalankan docker
Kalau sudah siap semua. Nginx siap, dockerfile siap. Kita tinggal jalankan perintah:
docker compose up -d
Dan boom! Sekarang php-fpm kita udah running dan nginx akan mulai mengarahkan request php ke port yang sudah kita atur (jangan lupa restart service nginx).
Cara Eksekusi Artisan? Composer?
Karena php tidak terinstall di mesin host (dalam kasus kami karena OS udah terlalu tua), maka untuk eksekusi artisan mau pun composer harus dari dalam kontainer.
Caranya kita bisa langsung pakai perintah docker compose dari direktori yang aktif.
Cara masuk ke dalam bash:
docker compose exec [nama-service] -it bash
Nama service adalah php sesuai yang ada di dalam file docker-compose.yml.
Perintah di atas akan memindahkan kita dari mesin host ke dalam container yang sudah kita buat.