Table of Contents

Docker LEMP + PostgreSQL + MSSQL

Kebutuhan

  1. Docker Desktop
  2. Jika menggunakan Windows Install WSL dan Konfigurasi

Identifikasi Container

Docker merekomendasikan untuk menjalankan hanya satu proses per container, yang secara umum berarti bahwa setiap container harus menjalankan satu perangkat lunak. Mari kita bedah program apa saja yang mendasari LEMP:

  1. L is for Linux;
  2. E is for Nginx;
  3. M is for MySQL;
  4. P is for PHP.

Linux adalah sistem operasi yang dijalankan Docker, sehingga kita hanya memiliki Nginx, MySQL, dan PHP. Untuk mempermudah, kita juga akan menambahkan phpMyAdmin ke dalamnya. Oleh karena itu, kita sekarang membutuhkan container berikut:

  • satu container untuk Nginx;
  • satu container untuk PHP (PHP-FPM);
  • satu container untuk MySQL;
  • satu container untuk phpMyAdmin.

Nah disini kita akan menyiapkan container  dan membuat container tersebut dapat berinteraksi satu sama lain?

Docker Compose

Docker Desktop dilengkapi dengan alat bernama Docker Compose yang memungkinkan Anda mendefinisikan dan menjalankan aplikasi Docker multi-container(jika sistem Anda berjalan di Linux, Anda perlu menginstalnya secara terpisah).

Container diatur di dalam file konfigurasi YAML dan Docker Compose akan menangani pembuatan image dan memulai container, serta beberapa hal lainnya seperti menghubungkan container secara otomatis ke jaringan internal.

Nginx

File konfigurasi YAML adalah awal kita: buka editor teks favorit Anda dan tambahkan file docker-compose.yml baru ke direktori pilihan Anda di mesin lokal (komputer Anda), dengan konten berikut:

				
					# Services
services:

  # Nginx Service
  nginx:
    image: nginx:1.27
    ports:
      - 80:80
				
			

Penjelasan :

  1. key services, yang merupakan daftar komponen aplikasi. Untuk saat ini kita hanya memiliki service nginx, dengan beberapa key: image dan ports.
    1. key image, menunjukkan  versi image yang akan digunakan untuk membangun container service; dalam kasus ini, versi 1.27 dari Nginx image. Buka tautan Nginx image di tab baru: ini akan membawa Anda ke Docker Hub, yang merupakan registri terbesar untuk image container.
    2. key ports, menunjukkan bahwa kita ingin memetakan port 80 mesin lokal kita (digunakan oleh HTTP) ke port container. Dengan kata lain, ketika kita mengakses port 80 di mesin lokal kita (yaitu komputer Anda), kita akan diteruskan ke port 80 dari container Nginx.

Buka  terminal jalan kan docker compose :

				
					docker compose up -d
				
			

Penjelasan :

  1. composer up -d, pada dasarnya kita meminta Docker Compose untuk membuat dan memulai container berdasarkan file docker-compose.yml
  2. option -d, merupakan pilihan bahwa kita ingin menjalankan container di background sehingga kita tetap dapat menggunakan terminal.

Buka web browser anda, lalu kikan http:localhost

Untuk melihat container apa saja yang sedang berjalan, ketikan perintah berikut :

				
					docker compose ps
				
			

Dan untuk menghentikan container ketikan perintah berikut :

				
					docker compose stop
				
			

Pada titik ini, Anda mungkin bertanya-tanya apa perbedaan antara service, image, dan container. Service hanyalah salah satu komponen aplikasi Anda, seperti yang tercantum di docker-compose.yml. Setiap service mengacu pada image, dan container berisi beberapa service yang terkait.

Untuk perintah lengkap docker compose dapat dilihat disini.

PHP

Pada bagian ini, Nginx akan menyajikan file index.php sederhana melalui PHP-FPM, yang merupakan manajer proses yang paling banyak digunakan untuk PHP.

Ubah konten docker-compose.yml dengan yang berikut ini:

				
					# Services
services:

  # Nginx Service
  nginx:
    image: nginx:1.27
    ports:
      - 80:80
    volumes:
      - ./src:/var/www/php
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - php

  # PHP Service
  php:
    image: php:8.2-fpm
    working_dir: /var/www/php
    volumes:
      - ./src:/var/www/php
				
			

Beberapa hal terjadi di sini: mari kita lupakan layanan Nginx sejenak. Kita fokus pada service PHP yang baru saja ditambahkan.

Kita mulai dari key image php:8.2-fpm, sesuai dengan tag 8.2-fpm image dari hub resmi PHP, kita akan menggunakan php versi 8.2 dari PHP-FPM.

Volume

Mari lewati working_dir untuk saat ini, dan lihat key volume pada kedua service. /var/www/php merupakan link/tautan dari folder src pada direktori lokal. Service nginx dan php berbagi volume pada direktori yang sama.

Buat direktori src (pada level yang sama dengan docker-compose.yml) dan tambahkan file index.php berikut ke dalamnya:

				
					<?php
phpinfo();
?>
				
			

Selanjutnya terkait konfigurasi nginx pada service nginx yang akan mengarah ke kode aplikasi kita:

				
					- ./nginx/conf.d:/etc/nginx/conf.d
				
			

/etc/nginx/conf.d merupakan link dari nginx/conf.d. Nginx secara otomatis membaca file yang diakhiri dengan .conf yang terletak di direktori /etc/nginx/conf.d.

Buat folder nginx/conf.d dan tambahkan file default.conf berikut ke dalamnya:

				
					server {
    listen 80 default_server;
    listen [::]:80 default_server;
    root   /var/www/php;
    index  index.php;

    location ~* \.php$ {
        fastcgi_pass   php:9000;
        include        fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param  SCRIPT_NAME     $fastcgi_script_name;
    }
}
				
			

Kita mengarahkan root ke /var/www/php, yang merupakan direktori tempat kita memasang kode aplikasi kita di container Nginx dan PHP, dan kita menyetel indeks ke index.php.

Perhatikan juga baris berikut :

				
					fastcgi_pass php:9000;
				
			

Ini memberitahu Nginx untuk meneruskan permintaan file PHP ke port kontainer PHP 9000, yang merupakan port default dari PHP-FPM. Secara internal, Docker Compose akan secara otomatis menyelesaikan kata kunci php ke alamat IP pribadi apa pun yang ditetapkan ke container PHP.

Ini merupakan salah satu fitur dari Docker Compose dimana saat start-up, ia akan secara otomatis menyiapkan jaringan internal di mana setiap container dapat ditemukan melalui nama layanannya.

Urutan Run Container

Terakhir, mari kita lihat bagian konfigurasi terakhir dari layanan Nginx:

				
					depends_on:
  - php
				
			

Terkadang, urutan Docker Compose memulai container itu penting. Karena kita ingin Nginx meneruskan permintaan PHP ke port kontainer PHP 9000, kesalahan berikut mungkin terjadi jika Nginx sudah siap sebelum PHP:

				
					[emerg] 1#1: host not found in upstream "php" in /etc/nginx/conf.d/php.conf:7
nginx_1  | nginx: [emerg] host not found in upstream "php" in /etc/nginx/conf.d/php.conf:7
nginx_1 exited with code 1
				
			

Hal ini menyebabkan proses Nginx terhenti, dan karena container Nginx hanya akan berjalan selama proses Nginx aktif, container tersebut juga akan berhenti. Konfigurasi depend_on memastikan container PHP akan dimulai sebelum Nginx, sehingga menyelamatkan kita dari situasi di atas.

Direktori dan struktur file Anda sekarang akan terlihat seperti ini:

				
					lemp/
├── nginx/
│   └── conf.d/
│           └── php.conf
├── src/
│   └── index.php
└── docker-compose.yml
				
			

Sekarang kita siap untuk menjalankan test kedua yaitu menjalankan php di dalam webserver nginx. Kembali ke terminal Anda dan jalankan perintah yang sama lagi (kali ini, image PHP akan diunduh):

				
					docker compose up -d
				
			

Sekarang buka browser, lalu akses kembali “http:localhost” :

Working Direktory

Fungsi working_diretory pada service php. Jika kita mejalankan docker compose ps, kita akan melihat dua container yg sedang berjalan. Mari kita periksa container PHP:

				
					NAME           IMAGE         COMMAND                  SERVICE   CREATED          STATUS         PORTS
lemp-nginx-1   nginx:1.27    "/docker-entrypoint.…"   nginx     12 minutes ago   Up 5 seconds   0.0.0.0:80->80/tcp
lemp-php-1     php:8.2-fpm   "docker-php-entrypoi…"   php       12 minutes ago   Up 5 seconds   9000/tcp
				
			
				
					docker compose exec php bash
				
			

Dengan menjalankan perintah di atas, kita meminta Docker Compose untuk mengeksekusi Bash pada container PHP. Anda akan mendapatkan prompt baru yang menunjukkan bahwa Anda saat ini berada di direktori /var/www/php. Itulah fungsi working_direktory. Jalankan perintah ls sederhana untuk melihat daftar isi direktori: Anda akan melihat index.php, yang merupakan link dari folder src lokal yang ditautkan ke folder /var/www/php  pada container.

Ketikan exit untuk keluar dari terminal bash container.

Log

Sebelum kita melanjutkan ke bagian berikutnya, ada  satu trik terakhir. Kembali ke terminal Anda dan jalankan perintah berikut:

				
					docker compose logs -f
				
			

Tunggu hingga beberapa log ditampilkan, buka webbrowser anda kembali dan arahkan ke http://localhost lalu klik tombol refresh beberapa kali maka terminal anda akan menunjukan log berikut :

MySQL

Komponen kunci terakhir dari LEMP adalah MySQL. Mari perbarui docker-compose.yml lagi:

				
					# Services
services:

  # Nginx Service
  nginx:
    image: nginx:1.27
    ports:
      - 80:80
    volumes:
      - ./src:/var/www/php
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - php

  # PHP Service
  php:
    build: ./.docker/php
    working_dir: /var/www/php
    volumes:
      - ./src:/var/www/php
    depends_on:
      mysql:
        condition: service_healthy

  # MySQL Service
  mysql:
    image: mysql/mysql-server:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_ROOT_HOST: "%"
      MYSQL_DATABASE: demo
    volumes:
      - ./.docker/mysql/my.cnf:/etc/mysql/my.cnf
      - mysqldata:/var/lib/mysql
    healthcheck:
      test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
      interval: 5s
      retries: 10

# Volumes
volumes:

  mysqldata:
				
			

Service Nginx masih sama, namun kita akan melakukan sedikit pembaruan pada service PHP. Kita sudah familiar dengan depend_on: kali ini, kita akan memberitahukan bahwa service MySQL harus dimulai sebelum PHP. Perbedaan lainnya adalah hadirnya opsi kondisi, tapi sebelum saya menjelaskan semuanya, mari kita lihat bagian build baru dari layanan PHP yang sepertinya menggantikan versi image nya. Daripada menggunakan image PHP resmi apa adanya, kami memberi tahu Docker Compose untuk menggunakan Dockerfile dari .docker/php untuk membuat image baru.

Dockerfile

Docker memiliki panduan untuk membuat image: setiap image memilikinya, bahkan image resmi (contoh misalnya Nginx). kita memberi tahu Docker Compose untuk menggunakan Dockerfile dari .docker/php untuk membuat image baru.

Buat folder .docker/php dan tambahkan file bernama Dockerfile ke dalamnya, dengan konten berikut:

				
					FROM php:8.2-fpm

RUN docker-php-ext-install pdo_mysql
				
			

PHP memerlukan ekstensi pdo_mysql untuk membaca dari database MySQL. Meskipun tidak disertai dengan image resminya, Docker Hub memberikan beberapa instruksi untuk menginstal ekstensi PHP dengan mudah.

Di bagian atas Dockerfile, kita akan memanggil image dengan versinya dan dilanjutkan dengan menginstal pdo_mysql dengan perintah RUN. Dan saat berikutnya kita memulai container, Docker Compose akan mengambil perubahan dan membuat image baru berdasarkan Dockerfile yang diberikan.

Masih banyak lagi yang bisa dilakukan dengan Dockerfile,  contoh di atas sangat dasar, beberapa kasus penggunaan tingkat lanjut akan dibahas di artikel berikutnya.

Untuk saat ini, mari perbarui index.php untuk memanfaatkan ekstensi baru:

				
					<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Hello there</title>
        <style>
            body {
                font-family: "Arial", sans-serif;
                font-size: larger;
            }

            .center {
                display: block;
                margin-left: auto;
                margin-right: auto;
                width: 50%;
            }
        </style>
    </head>
    <body>
        <img decoding="async" src="https://tech.osteel.me/images/2020/03/04/hello.gif" alt="Hello there" class="center">
        <?php
        $connection = new PDO('mysql:host=mysql;dbname=demo;charset=utf8', 'root', 'root');
        $query      = $connection->query("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'demo'");
        $tables     = $query->fetchAll(PDO::FETCH_COLUMN);

        if (empty($tables)) {
            echo '<p class="center">There are no tables in database <code>demo</code>.</p>';
        } else {
            echo '<p class="center">Database <code>demo</code> contains the following tables:</p>';
            echo '<ul class="center">';
            foreach ($tables as $table) {
                echo "<li>{$table}</li>";
            }
            echo '</ul>';
        }
        ?>
    </body>
</html>
				
			

Kode di atas merupakan kode PHP untuk menghubungkan ke database yang belum ada.

Sekarang mari kita lihat lebih dekat layanan MySQL di docker-compose.yml:

				
					# MySQL Service
mysql:
  image: mysql/mysql-server:8.0
  environment:
    MYSQL_ROOT_PASSWORD: root
    MYSQL_ROOT_HOST: "%"
    MYSQL_DATABASE: demo
  volumes:
    - ./.docker/mysql/my.cnf:/etc/mysql/my.cnf
    - mysqldata:/var/lib/mysql
  healthcheck:
    test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
    interval: 5s
    retries: 10
				
			

Bagian image menunjuk ke image MySQL Server untuk versi 8.0, dan diikuti oleh enviroment yang belum kita buat sebelumnya. Ini berisi tiga kunci:

  1. MYSQL_ROOT_PASSWORD,
  2. MYSQL_ROOT_HOST
  3. MYSQL_DATABASE

yang merupakan variabel enviroment yang akan ditetapkan pada container saat pembuatan. Dimungkinkan untuk mengatur kata sandi root, mengotorisasi koneksi dari alamat IP mana pun, dan membuat database default masing-masing.

Dengan kata lain, database demo akan otomatis dibuat untuk kita saat container dimulai.

MySql Setting

Setelah variable enviroment adalah volume yang sebelumnya sudah pernah dibuat. Volume pertama adalah file konfigurasi yang akan kita gunakan untuk mengatur set karakter ke utf8mb4_unicode_ci secara default, yang cukup standar saat ini.

Buat folder mysql dan tambahkan file my.cnf berikut ke dalamnya:

				
					[mysqld]
collation-server     = utf8mb4_unicode_ci
character-set-server = utf8mb4
default-authentication-plugin = mysql_native_password
				
			

Jika container sudah berjalan, destroy container beserta volumenya dengan docker composer down -v dan jalankan docker composer up -d lagi.

Volume Tetap

Volume kedua terlihat sedikit berbeda dari apa yang telah kita lihat sejauh ini: alih-alih menunjuk ke folder lokal, volume ini mengacu pada volume bernama yang ditentukan di bagian volume baru yang berada pada level yang sama dengan layanan:

				
					# Volumes
volumes:

  mysqldata:
				
			

Kita memerlukan jenis volume seperti itu karena tanpanya, setiap kali container service mysql di destroy, database juga ikut di destroy. Untuk membuatnya persisten(tetap), kita memberi tahu container MySQL untuk menggunakan volume mysqldata untuk menyimpan data secara lokal, lokal menjadi driver default (sama seperti jaringan, volume dilengkapi dengan berbagai driver dan opsi yang dapat Anda pelajari di sini). Hasilnya, direktori lokal dipasang ke container, perbedaannya adalah alih-alih kita yang menentukan lokasinya, kita membiarkan Docker Compose yang menentukan lokasinya.

Healtcheck

Bagian terakhir yang merupakan hal baru yaitu: healthcheck .Hal ini memungkinkan kita untuk menentukan pada kondisi mana sebuah container siap, dan bukan baru dimulai. Dalam hal ini, memulai container MySQL saja tidak cukup – kami juga ingin membuat database sebelum container PHP mencoba mengaksesnya. Dengan kata lain, tanpa pemeriksaan kesehatan ini, container PHP mungkin mencoba mengakses database meskipun database tersebut belum ada, sehingga menyebabkan kesalahan koneksi.

				
					depends_on:
  mysql:
    condition: service_healthy
				
			

Secara default, depend_on hanya akan menunggu container yang direferensikan dimulai, kecuali kami menentukan sebaliknya. Namun pemeriksaan kesehatan ini mungkin tidak berhasil pada percobaan pertama; itu sebabnya kami mengaturnya untuk mencoba lagi setiap 5 detik hingga 10 kali, masing-masing menggunakan tombol interval dan coba lagi.

Healtcheck sendiri menggunakan mysqladmin, sebuah program administrasi server MySQL, untuk melakukan ping ke server hingga mendapat respons. Ia melakukannya dengan menggunakan pengguna root dan nilai yang ditetapkan dalam variabel enviroment salah satunya MYSQL_ROOT_PASSWORD sebagai kata sandi.

Kembali ke terminal Anda dan jalankan docker composer up -d lagi. Setelah selesai mengunduh image MySQL dan semua container sudah aktif dan berjalan, buka browsr dan refresh localhost. Anda akan melihat ini:

Sekarang kita memiliki Nginx yang menjalankan PHP yang dapat terhubung ke database MySQL, artinya LEMP sudah terpenuhi. Langkah selanjutnya adalah memperbaiki pengaturan kita, dimulai dengan melihat bagaimana kita dapat berinteraksi dengan database dengan cara yang lebih mudah.

phpMyAdmin

Ketika berurusan dengan database MySQL, phpMyAdmin tetap menjadi pilihan populer; mudahnya, mereka menyediakan image Docker yang cukup mudah untuk diatur.

Namun jika anda terbiasa dengan beberapa alat lain seperti Sekuel Ace, Navicat atau MySQL Workbench, Anda cukup memperbarui konfigurasi MySQL di docker-compose.yml dan menambahkan bagian port yang memetakan port 3306 mesin lokal Anda ke kontainer:

				
					...
# MySQL Service
mysql:
    ports:
        - 3306:3306
    ...
...
				
			
Menambahkan Service PhpMyAdmin

Buka docker-compose.yml untuk terakhir kalinya dan tambahkan konfigurasi layanan berikut setelah MySQL:

				
					# PhpMyAdmin Service
phpmyadmin:
  image: phpmyadmin/phpmyadmin:5
  ports:
    - 8080:80
  environment:
    PMA_HOST: mysql
  depends_on:
    mysql:
      condition: service_healthy
				
			

Kita mulai dari image versi 5 dan kita memetakan port mesin lokal 8080 ke port kontainer 80. Kita menunjukkan bahwa kontainer MySQL harus dimulai dan siap terlebih dahulu dengan depend_on, dan mengatur host yang harus dihubungkan oleh phpMyAdmin menggunakan lingkungan PMA_HOST variabel (ingat bahwa Docker Compose akan secara otomatis menyelesaikan mysql ke alamat IP pribadi yang ditetapkan ke container).

Simpan perubahan dan jalankan docker composer up -d lagi. Image akan diunduh, lalu setelah semuanya beres, kunjungi localhost:8080:

Postgres SQL

Jika anda ingin menambahkan Service Postgre SQL berikut baris inisialisasinya pada docker-compose.yml

				
					...
  # PHP Service
  php:
    ...
    depends_on:
      ...
      postgres:
        condition: service_healthy
...  
  #PosgreSQL Service
  postgres:
    image: postgres
    restart: always
    environment:
      #POSTGRES_DB: postgres
      #POSTGRES_USER: postgres
      POSTGRES_PASSWORD: root
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 1s
      timeout: 5s
      retries: 10
...
# Volumes
volumes:

  ...
  pgdata:
  ...
				
			

Dan tambahkan baris berikut pada php/Dockerfile

				
					...
RUN apt-get update \
 ...
 && apt-get install -y libpq-dev \
 && docker-php-ext-install pgsql pdo_pgsql pdo\
...
				
			

Buat file baru src/mssql-test.php

				
					<?php
    $connection = pg_connect ("host=postgres dbname=postgres user=postgres password=root");
    if($connection) {
       echo 'connected';
    } else {
        echo 'there has been an error connecting';
    } 
?>
				
			

Perhatikan bagian `host=posgres` , hal tersebut dikarenakan container tidak dapat membaca localhost melainkan nama service dalam container itu sendiri. Untuk melihat nama service yang sedang berjalan kerikan perintah  docker compose ps.

Hasilnya sbb :

SQL Server 2019

Jika anda ingin menambahkan Service Sql Server berikut baris inisialisasinya pada docker-compose.yml

				
					...
# MSSQL Services
  mssql:
    # SQL Server image
    image: mcr.microsoft.com/mssql/server:2019-latest
    ports:
      # Map host port 1433 to container port 1433
      - "1433:1433"
    environment:
      # Accept the End-User License Agreement
      - ACCEPT_EULA=Y
      # Set the sa user password for the SQL Server
      - SA_PASSWORD=YourStrong!Password
    # persist SQL Server data
    volumes:
      - mssql-srv:/var/opt/mssql
...
# Volumes
volumes:

  ...
  mssql-srv:
				
			

Dan tambahkan baris berikut pada php/Dockerfile

				
					RUN apt-get update \
 ...
 # mssql headers
 && apt-get install -y gnupg2

ENV ACCEPT_EULA=Y

RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -

RUN curl https://packages.microsoft.com/config/debian/11/prod.list > /etc/apt/sources.list.d/mssql-release.list

# Update package lists and install dependencies
RUN apt-get update && apt-get install -y \
    unixodbc-dev \
    unixodbc \
    msodbcsql17

# Install pdo_sqlsrv extension
RUN pecl install sqlsrv pdo_sqlsrv
RUN docker-php-ext-enable sqlsrv pdo_sqlsrv
				
			

Buat file baru src/mssql-test.php

				
					<?php
try {
    // Establish a connection to the SQL Server using PDO
    $conn = new PDO("sqlsrv:server=mssql", "sa", "YourStrong!Password");

    // Set PDO attributes to enable error reporting and exceptions
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Execute a query to get the SQL Server version
    $q = $conn->query('SELECT @@VERSION');
    
    // Display the SQL Server version
    echo 'MSSQL VERSION: ' . $q->fetchColumn() . '<br>';
} catch (Exception $e) {
    // Error message and terminate the script
    die(print_r($e->getMessage()));
}
// Display the PHP version
echo 'PHP VERSION: ' . phpversion() . '<br>';
				
			

Hasilnya sbb :