Lewati ke isi

04 โ€” Docker

โ† Database | Index | Lanjut: Security โ†’


4.1 Masalah yang Diselesaikan Docker

Keluhan klasik: "Di laptop saya jalan kok!" ๐Ÿคท

Aplikasi butuh environment spesifik: versi PHP/Node tertentu, extension, library sistem, konfigurasi OS. Perbedaan kecil antar mesin = bug misterius yang menghabiskan waktu berhari-hari.

Docker membungkus aplikasi + seluruh environment-nya ke dalam satu paket yang berjalan identik di mana pun โ€” laptop developer, laptop QA, server staging, server production.

Analogi: kontainer pengiriman barang. Isi kontainer bisa apa saja (furnitur, elektronik, makanan), tapi karena bentuknya standar, semua kapal/truk/crane bisa mengangkutnya tanpa peduli isinya. Sebelum kontainer distandarkan, bongkar-muat kapal butuh berhari-hari; sesudahnya, hitungan jam. Docker melakukan hal yang sama untuk software.

4.2 Container vs Virtual Machine

Keduanya "mengisolasi environment", tapi caranya berbeda:

   VIRTUAL MACHINE                        CONTAINER
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  App A    โ”‚  App B    โ”‚            โ”‚  App A   โ”‚  App B   โ”‚  App C   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค            โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Guest OS  โ”‚ Guest OS  โ”‚  โ† berat!  โ”‚        Docker Engine            โ”‚
โ”‚ (GB-an)   โ”‚ (GB-an)   โ”‚            โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค            โ”‚           Host OS               โ”‚
โ”‚      Hypervisor       โ”‚            โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค            โ”‚           Hardware              โ”‚
โ”‚        Host OS        โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚        Hardware       โ”‚            Berbagi kernel host OS โ†’
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜            ringan (MB-an), start dalam detik
VM Container
Ukuran GB-an (OS lengkap) MB-an (hanya aplikasi + dependency)
Waktu start Menit Detik
Isolasi Sangat kuat (OS terpisah) Cukup kuat (proses terisolasi)
Kapan dipakai Butuh OS berbeda, isolasi maksimal Default untuk aplikasi modern

Catatan konteks: platform virtualisasi seperti Proxmox menjalankan VM; di dalam VM itu biasanya berjalan banyak container Docker. Keduanya saling melengkapi, bukan bersaing.

4.3 Konsep Inti

Istilah Definisi Analogi
Image Template/blueprint aplikasi + environment (read-only) Resep kue
Container Instance yang berjalan dari sebuah image Kue yang dipanggang dari resep โ€” satu resep, banyak kue
Dockerfile Skrip untuk membangun image Tulisan resepnya
Registry Tempat menyimpan & berbagi image (Docker Hub, GHCR, ECR) Toko buku resep
Tag Versi sebuah image (node:20-alpine, myapp:1.4.2) Edisi cetakan buku resep
Volume Penyimpanan data persisten di luar container Lemari es terpisah dari dapur
Network Jaringan virtual antar container Interkom antar ruangan
Port mapping Menghubungkan port host ke port container Nomor ekstensi telepon kantor

๐Ÿ“Œ Wajib paham โ€” container itu ephemeral (sekali pakai): filesystem di dalam container hilang saat container dihapus/dibuat ulang. Data yang harus hidup lebih lama dari container-nya (database, file upload) wajib di volume. Ini penyebab klasik "data database hilang setelah update".

4.4 Dockerfile

# Mulai dari image dasar โ€” SELALU pakai versi spesifik, bukan :latest
FROM node:20-alpine

# Folder kerja di dalam container
WORKDIR /app

# Copy file dependency DULUAN, baru install
# โ†’ memanfaatkan layer caching (lihat penjelasan di bawah)
COPY package*.json ./
RUN npm ci --omit=dev

# Baru copy seluruh kode aplikasi
COPY . .

# Dokumentasi port yang dipakai aplikasi
EXPOSE 3000

# Jalankan sebagai user non-root (keamanan)
USER node

# Perintah saat container dijalankan
CMD ["node", "server.js"]

Layer caching โ€” kenapa urutan baris itu penting

Setiap instruksi Dockerfile membentuk layer yang di-cache. Kalau sebuah layer tidak berubah, build berikutnya memakai cache-nya:

COPY package*.json ./   โ† jarang berubah  โ†’ cache dipakai
RUN npm ci              โ† hanya diulang kalau package.json berubah
COPY . .                โ† sering berubah (setiap edit kode)

Kalau COPY . . ditaruh sebelum npm ci, setiap edit kode sekecil apa pun memaksa install ulang semua dependency โ†’ build 5 menit alih-alih 10 detik. Ini salah satu kesalahan yang paling sering dibuat AI.

.dockerignore

Seperti .gitignore tapi untuk build Docker โ€” mencegah file tak perlu (dan berbahaya) masuk ke image:

node_modules
.git
.env          โ† PENTING: secret jangan sampai terbawa ke image!
*.log

Multi-stage build (sekilas)

Untuk aplikasi yang perlu di-build (Next.js, Go, dsb.): stage pertama berisi semua tool build (berat), stage akhir hanya berisi hasil build (ringan):

FROM node:20-alpine AS builder      # stage 1: build
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM node:20-alpine                 # stage 2: hanya hasil akhirnya
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
CMD ["npm", "start"]

Hasil: image production kecil, cepat di-deploy, dan permukaan serangannya lebih sempit.

4.5 Perintah Dasar

# --- Image ---
docker build -t my-app:1.0 .        # bangun image dari Dockerfile, beri tag
docker images                        # daftar image lokal
docker pull postgres:16              # unduh image dari registry

# --- Container ---
docker run -d -p 8080:3000 --name web my-app:1.0
#          โ”‚  โ”‚            โ”‚
#          โ”‚  โ”‚            โ””โ”€โ”€ beri nama biar gampang dirujuk
#          โ”‚  โ””โ”€โ”€ port host 8080 โ†’ port container 3000
#          โ””โ”€โ”€ detached (jalan di background)

docker ps                            # container yang berjalan
docker ps -a                         # termasuk yang sudah berhenti
docker stop web && docker rm web     # hentikan lalu hapus

# --- Debugging (tiga perintah paling sering dipakai) ---
docker logs web                      # lihat log โ€” TEMPAT PERTAMA saat ada masalah
docker logs -f --tail 100 web        # ikuti log live, 100 baris terakhir
docker exec -it web sh               # "masuk" ke dalam container
docker inspect web                   # semua detail konfigurasi container
docker stats                         # penggunaan CPU/RAM per container

4.6 Volume & Network

Volume โ€” dua cara umum

# Named volume โ€” dikelola Docker, untuk data aplikasi (database!)
docker run -v db_data:/var/lib/postgresql/data postgres:16

# Bind mount โ€” folder host dipetakan ke container, untuk development
docker run -v ./src:/app/src my-app     # edit kode di host, langsung terasa di container

Network โ€” cara container saling bicara

Container dalam satu network Docker saling memanggil lewat nama service, bukan localhost:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ network: myapp_default โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚   app   โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚   db    โ”‚        โ”‚  redis  โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  "db:5432"โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โš ๏ธ Red flag klasik: kode di dalam container memakai DB_HOST=localhost. Di dalam container, localhost = container itu sendiri, bukan mesin host, bukan container database! Yang benar: nama service (DB_HOST=db). Ini penyebab error connection refused nomor satu bagi pemula.

4.7 Docker Compose โ€” Multi-Container

Aplikasi nyata butuh beberapa service sekaligus. Docker Compose mendefinisikan semuanya dalam satu file:

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "8080:3000"
    environment:
      DB_HOST: db                    # nama service = hostname
      DB_PASSWORD: ${DB_PASSWORD}    # diambil dari file .env
    depends_on:
      db:
        condition: service_healthy   # tunggu db BENAR-BENAR siap
    restart: unless-stopped          # auto-restart kalau crash

  db:
    image: postgres:16               # versi spesifik, bukan :latest
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:                     # definisi "siap" untuk service ini
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  db_data:
  redis_data:

Perintah harian:

docker compose up -d          # jalankan semua service
docker compose ps             # status semua service
docker compose logs -f app    # log service tertentu
docker compose down           # hentikan & hapus container (volume AMAN)
docker compose down -v        # โš ๏ธ hapus SEKALIGUS VOLUME โ€” data hilang!
docker compose up -d --build  # rebuild setelah kode berubah

๐Ÿ“Œ Perhatikan depends_on + healthcheck: tanpa healthcheck, depends_on hanya menunggu container start, bukan siap โ€” aplikasi bisa crash karena mencoba konek ke database yang masih booting. AI sering melewatkan ini.

4.8 Environment Variable & File .env

Konfigurasi yang berbeda antar environment (dan semua secret) hidup di environment variable, bukan di kode:

.env                       โ† nilai asli โ€” MASUK .gitignore
.env.example               โ† template tanpa nilai asli โ€” DI-COMMIT ke Git

# isi .env.example:
DB_HOST=db
DB_PASSWORD=              โ† kosong; tiap orang/environment mengisi sendiri
APP_ENV=local

Rantainya: .env โ†’ dibaca Docker Compose โ†’ dioper ke container โ†’ dibaca aplikasi.

โš ๏ธ Red flag: - Password/API key di-hardcode di Dockerfile, docker-compose.yml, atau kode. - .env tidak ada di .gitignore (dan .dockerignore). - AI menaruh nilai asli di .env.example.

4.9 Troubleshooting Umum

Gejala Kemungkinan Penyebab Cek Dengan
Container langsung mati setelah start Aplikasi crash saat boot; env variable kurang docker logs <nama>
port is already allocated Port host sudah dipakai proses lain Ganti port mapping, mis. 8081:3000
connection refused ke database Pakai localhost alih-alih nama service; atau DB belum siap Cek env DB_HOST; tambah healthcheck
Data hilang setelah update Data di dalam container, bukan di volume docker inspect bagian Mounts
Perubahan kode tidak muncul Image belum di-rebuild docker compose up -d --build
Disk server penuh Timbunan image/container lama docker system df, lalu docker system prune (hati-hati)

4.10 Docker dalam Alur Kerja Tim

Developer          Git push          CI/CD (GitHub Actions dsb.)         Server
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ  commit  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ  build image โ†’ test โ†’ push ke registry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ  pull image โ†’ jalankan

Karena yang berpindah adalah image yang identik, apa yang dites di CI = apa yang jalan di production. Inilah fondasi mengapa staging dan production bisa dipercaya konsisten.

Kenapa ini relevan untuk non-developer?

  • Designer/QA: jalankan seluruh aplikasi di laptop dengan satu perintah docker compose up -d โ€” tanpa install PHP/Node/MySQL manual. Mengecek hasil desain/fitur tidak perlu menunggu deploy.
  • PM: paham kenapa "environment parity" mengurangi bug kelas "cuma muncul di production", dan kenapa setup project baru bisa sangat cepat.
  • Semua orang: saat ada masalah, docker logs adalah informasi pertama yang berguna untuk dilaporkan.

4.11 Prompting AI untuk Docker

๐Ÿ’ก Setup baru: "Buatkan Dockerfile (multi-stage) dan docker-compose.yml untuk project Laravel 11 + MySQL 8 + Redis. Syarat: data MySQL di named volume; semua credential lewat .env (sertakan .env.example); healthcheck untuk MySQL dan app menunggu MySQL sehat; versi image spesifik, bukan latest; jalankan app sebagai user non-root; sertakan .dockerignore."

๐Ÿ’ก Debugging: "Container app restart terus-menerus. Ini output docker logs app 50 baris terakhir: [tempel log]. Diagnosis penyebabnya dan beri langkah perbaikan."

๐Ÿ’ก Review: "Review Dockerfile & compose ini: cek secret hardcoded, penggunaan :latest, urutan layer untuk caching, data penting yang tidak di volume, dan tidak adanya healthcheck."

๐Ÿงช Latihan: minta AI membuatkan docker-compose.yml berisi satu service nginx di port 8080, jalankan dengan docker compose up -d, buka http://localhost:8080, lihat lognya dengan docker compose logs, lalu matikan dengan docker compose down. Selamat โ€” Anda baru saja menjalankan server web dalam container.


4.12 ๐Ÿงช TUTORIAL PRAKTIK: Stack Pertamamu dalam 30 Menit

Prasyarat: Docker Desktop terpasang (docker.com/products/docker-desktop). Verifikasi:

docker --version          # Docker version 2x.x
docker compose version    # Docker Compose version v2.x

Langkah 1 โ€” Hello, container (5 menit)

docker run -d -p 8080:80 --name web nginx:1.27-alpine

Buka http://localhost:8080 โ†’ halaman "Welcome to nginx!". Anda baru saja menjalankan web server tanpa menginstal apa pun di OS Anda. Sekarang bedah:

docker ps            # lihat container 'web' berjalan, mapping 8080->80
docker logs web      # log akses nginx โ€” refresh browser, lihat log bertambah
docker exec -it web sh   # "masuk" ke dalamnya
  ls /usr/share/nginx/html   # file HTML yang disajikan
  exit
docker stop web && docker rm web

Langkah 2 โ€” Buktikan sendiri bahwa container itu ephemeral (5 menit)

Ini pelajaran paling penting bab ini โ€” alami langsung supaya tidak pernah lupa:

docker run -d --name db1 -e POSTGRES_PASSWORD=rahasia postgres:16-alpine
docker exec -it db1 psql -U postgres -c "CREATE TABLE catatan (isi TEXT); INSERT INTO catatan VALUES ('data penting!');"
docker exec -it db1 psql -U postgres -c "SELECT * FROM catatan;"   # data ada โœ…

docker rm -f db1                                                   # hapus container
docker run -d --name db1 -e POSTGRES_PASSWORD=rahasia postgres:16-alpine
docker exec -it db1 psql -U postgres -c "SELECT * FROM catatan;"   # ๐Ÿ’ฅ ERROR: tabel tidak ada
docker rm -f db1

Data hilang total. Sekarang ulangi dengan volume (-v db_data:/var/lib/postgresql/data di kedua docker run) โ€” data akan selamat melewati penghapusan container. Inilah kenapa checklist bab 06 mewajibkan volume.

Langkah 3 โ€” Compose stack mini (10 menit)

Buat folder baru berisi dua file:

# docker-compose.yml
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

  adminer:                      # GUI database ringan, buat eksplorasi
    image: adminer:4
    ports:
      - "8081:8080"
    depends_on:
      db:
        condition: service_healthy

volumes:
  db_data:
# .env
DB_PASSWORD=rahasia-lokal-saja

Jalankan dan verifikasi:

docker compose up -d
docker compose ps        # kedua service "running", db "(healthy)"

Buka http://localhost:8081 โ†’ login Adminer: server db (nama service โ€” bukan localhost!), user postgres, password dari .env. Anda kini punya database + GUI-nya, tanpa instalasi apa pun.

Langkah 4 โ€” Simulasi insiden & pemulihan (10 menit)

Latih refleks troubleshooting (ยง4.9):

  1. Ubah ports adminer jadi "8081:9999" (port dalam yang salah) โ†’ docker compose up -d โ†’ buka browser โ†’ gagal. Diagnosis: docker compose ps + docker compose logs adminer. Kembalikan.
  2. Ganti DB_PASSWORD di .env lalu docker compose up -d โ†’ Adminer tidak bisa login dengan password baru. Kenapa? Password Postgres ditetapkan saat volume pertama kali dibuat โ€” env baru tidak mengubah data lama. Pelajaran: konfigurasi vs data itu dua hal berbeda. (Pemulihan di latihan ini: docker compose down -v lalu up ulang โ€” dan sadari bahwa -v barusan menghapus semua data. Di production, langkah ini = bencana.)

Langkah 5 โ€” Serahkan ke AI dengan konteks penuh

Sekarang Anda punya vocabulary dan intuisi untuk menilai. Tes AI:

๐Ÿ’ก "Tambahkan service redis:7-alpine ke compose ini [tempel file]: dengan volume, healthcheck, dan tanpa mengekspos port ke host (hanya diakses antar-container). Jelaskan kenapa tidak perlu port mapping."

Verifikasi jawabannya dengan pemahaman dari Langkah 1โ€“4. Kalau AI mengekspos port Redis ke host tanpa alasan โ€” Anda kini tahu itu perlu dipertanyakan.


Rangkuman Bab

  • Docker = environment identik di mana pun; solusi "di laptop saya jalan".
  • Image = blueprint; container = instance; container itu sekali pakai โ€” data wajib di volume.
  • Urutan instruksi Dockerfile menentukan kecepatan build (layer caching).
  • Antar container: panggil lewat nama service, bukan localhost.
  • Compose mendefinisikan semua service dalam satu file; down -v menghapus data โ€” hati-hati.
  • Secret lewat env variable + .env (di-gitignore); .env.example sebagai template.
  • docker logs adalah langkah pertama semua debugging.

โ† Database | Index | Lanjut: Security โ†’