Belajar Docker Dockerfile Dan Docker Compose

| | | Docker
Belajar Docker Dockerfile Dan Docker Compose

momen ketika Docker benar-benar terasa berguna bagi saya adalah saat sebuah project butuh aplikasi, database, dan cache sekaligus, di komputer saya jalan, di komputer teman saya error, padahal kodenya sama persis. dengan Docker Compose, semuanya berjalan dengan satu perintah di semua komputer, termasuk server produksi.

ada dua fondasi yang perlu dikuasai: Dockerfile untuk mendefinisikan satu aplikasi jadi image, dan Docker Compose untuk merangkai banyak service jadi satu kesatuan. keduanya kita bahas dari nol di artikel ini, baris per baris, plus studi kasus nyata aplikasi web dengan database MySQL.

kalau kalian belum kenal konsep container sama sekali dan belum install Docker, saya sarankan mampir dulu ke artikel dan cara install docker desktop di windows, supaya artikel ini tinggal fokus ke Dockerfile dan Compose-nya.

Konsep Singkat: Image, Container, Dockerfile

sebelum nulis kode, tiga istilah ini wajib nyambung dulu:

  • Image adalah cetakan aplikasi, hasil build dari Dockerfile. sifatnya read-only, seperti file installer aplikasi
  • Container adalah hasil menjalankan image, proses hidup yang bisa dijalankan, dihentikan, dan dihapus
  • Dockerfile adalah resep untuk membangun image, file teks berisi instruksi langkah demi langkah

analogi paling gampang: Dockerfile itu resep masakan, image itu masakan yang sudah jadi dan dibekukan, container itu masakan yang sedang dihidangkan di meja. satu image bisa menghasilkan banyak container, persis satu resep bisa memasak berkali-kali.

Dockerfile: Resep Image

kita mulai dengan contoh lengkap aplikasi Python sederhana. buat file bernama Dockerfile di root project:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

mari bedah tiap barisnya supaya tidak cuma copas tanpa paham:

  • FROM python:3.11-slim menentukan base image. slim artinya versi ramping Debian, jauh lebih kecil dari versi penuh. semua instruksi berikutnya dibangun di atas image ini
  • WORKDIR /app membuat folder kerja /app di dalam container dan memindahkan posisi kerja ke sana. kalau folder tidak ada, otomatis dibuat. instruksi setelahnya berjalan di folder ini
  • COPY requirements.txt . menyalin file requirements.txt dari komputer kalian ke dalam folder kerja di image
  • RUN pip install -r requirements.txt menjalankan perintah saat proses build, hasilnya terekam permanen di image
  • COPY . . menyalin seluruh isi project ke dalam image
  • EXPOSE 5000 mendeklarasikan bahwa aplikasi memakai port 5000, ini petunjuk dokumentasi, port belum terbuka ke host sebelum dipetakan saat run
  • CMD ["python", "app.py"] menentukan perintah yang dijalankan saat container mulai. bedanya dengan RUN: RUN jalan saat build, CMD jalan saat container start

perhatikan urutan COPY requirements.txt lalu RUN pip install sebelum COPY . .. ini bukan kebetulan, ini teknik caching Docker. selama requirements.txt tidak berubah, layer hasil pip install dipakai ulang dari cache, jadi build berikutnya jauh lebih cepat dibanding menyalin seluruh project dulu baru install dependency.

untuk membangun image, jalankan di folder yang berisi Dockerfile:

docker build -t myapp .

-t myapp memberi nama image, titik di akhir adalah build context, yaitu folder yang diizinkan diakses Docker saat build. setelah selesai, cek hasilnya dengan:

docker images

lalu jalankan jadi container dan petakan portnya:

docker run -d -p 5000:5000 --name myapp-container myapp

-d menjalankan di background, -p 5000:5000 memetakan port 5000 host ke port 5000 container. buka http://localhost:5000 di browser, aplikasi sudah jalan di dalam container.

Multi-Stage Build: Image Kecil untuk Produksi

masalah klasik pemula: image hasil build sering gemuk luar biasa, karena membawa seluruh tool build, node_modules, dan file sementara yang sebenarnya tidak dibutuhkan saat aplikasi jalan. solusinya multi-stage build, memisahkan proses build dan hasil akhir dalam satu Dockerfile.

contoh untuk aplikasi frontend Node.js yang hasil akhirnya cuma file statis:

# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

cara kerjanya:

  • stage pertama diberi nama builder, memakai image node lengkap untuk install dependency dan compile project
  • stage kedua memulai dari nol dengan image nginx:alpine yang ramping, lalu COPY --from=builder mengambil hanya folder /app/dist hasil build dari stage pertama
  • hasil akhirnya image berisi nginx dan file statis saja, tanpa node_modules, tanpa tool build

bedanya signifikan. untuk project frontend pada umumnya, image tanpa multi-stage bisa 700MB lebih, dengan multi-stage turun ke sekitar 50MB. lebih cepat di-push ke registry, lebih hemat penyimpanan server, dan permukaan serangnya lebih kecil karena tool build tidak ikut terdeploy.

hasilnya seperti ini kalau dilihat dari ukuran:

docker images
# REPOSITORY   TAG       SIZE
# myapp        single    724MB
# myapp        multi     48MB

Docker Compose: Orkestrasi Banyak Container

aplikasi asli jarang jalan sendirian, biasanya butuh database, cache, dan service lain. daripada mengetik docker run panjang untuk tiap service, Docker Compose merangkai semuanya dalam satu file YAML dan menjalankannya dengan satu perintah.

buat file docker-compose.yml di root project:

# docker-compose.yml
services:
  web:
    build: .
    ports:
      - "5000:5000"
    depends_on:
      - db
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: myapp
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

mari bedah bagian-bagiannya:

  • services berisi daftar container yang akan dijalankan, di sini ada dua: web dan db
  • web.build: . menyuruh Compose membangun image service web dari Dockerfile di folder saat ini. alternatifnya image: nama-image kalau memakai image jadi dari registry
  • ports memetakan port host ke container, "5000:5000" artinya port 5000 di komputer kalian menuju port 5000 di container
  • depends_on: [db] memastikan container db dinyalakan lebih dulu sebelum web
  • db.image: mysql:8.0 service ini memakai image MySQL resmi tanpa build sendiri
  • environment mengisi variable yang dibaca image mysql untuk set password root dan nama database awal
  • volumes: db_data:/var/lib/mysql menyimpan data MySQL di volume bernama db_data supaya tidak hilang saat container dihapus
  • volumes: db_data: di paling bawah mendeklarasikan volume yang dipakai di atas

perhatikan saya tidak menulis version: '3.8' di baris pertama. baris version itu warisan Docker Compose v1 yang sudah tidak dipakai lagi, kalian akan sering lihat di tutorial lama dan boleh dihapus tanpa efek apa pun.

Menjalankan Compose

dari folder yang berisi docker-compose.yml:

# jalankan semua service di background
docker compose up -d

# lihat container yang sedang jalan
docker compose ps

# lihat log semua service
docker compose logs -f

# hentikan semua service
docker compose down

# hentikan dan hapus volume sekalian
docker compose down -v

up -d pertama kali otomatis membangun image service web, menarik image MySQL, membuat volume, dan menyalakan kedua container. perintah docker compose tanpa tanda hubung adalah yang berlaku sekarang, sedangkan docker-compose versi lama sudah tidak disarankan.

untuk studi kasus yang lebih kompleks dengan volume dan network custom, saya bahas tuntas di artikel .

Studi Kasus: Aplikasi Web + Database MySQL

sekarang rangkai contoh nyata supaya terasa alurnya. kita punya aplikasi Python Flask sederhana yang membaca data dari MySQL. struktur foldernya:

proyek/
├── app.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml

app.py berisi aplikasi yang membaca variable environment untuk koneksi database:

import os
from flask import Flask, jsonify
import pymysql

app = Flask(__name__)

@app.route('/')
def index():
    connection = pymysql.connect(
        host=os.environ.get('DB_HOST', 'localhost'),
        user='root',
        password=os.environ.get('DB_PASSWORD', ''),
        database=os.environ.get('DB_NAME', 'myapp'),
    )
    with connection.cursor() as cursor:
        cursor.execute('SELECT NOW() AS waktu')
        result = cursor.fetchone()
    connection.close()
    return jsonify({'waktu_database': str(result['waktu'])})

requirements.txt:

flask
pymysql

Dockerfile untuk aplikasinya:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["flask", "run", "--host=0.0.0.0"]

--host=0.0.0.0 penting karena kalau tidak, Flask cuma mendengarkan di localhost container dan tidak bisa diakses dari luar.

lalu docker-compose.yml yang merangkai aplikasi dengan MySQL:

services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      DB_HOST: db
      DB_PASSWORD: root
      DB_NAME: myapp
    depends_on:
      - db
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: myapp
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

rahasia utama di sini: aplikasi tidak connect ke localhost, melainkan ke db. di dalam jaringan Compose, nama service berlaku sebagai hostname, Compose otomatis membuat jaringan internal yang menghubungkan semua service. inilah yang sering bikin bingung pemula, koneksi localhost dari container web tidak akan pernah menemukan MySQL.

jalankan dengan docker compose up -d, buka http://localhost:5000, maka muncul JSON berisi waktu dari database. seluruh lingkungan, aplikasi plus database, jalan dengan satu perintah.

Masalah yang Sering Muncul

  • Cannot connect to MySQL at localhost → di dalam container, database tidak di localhost. pakai nama service dari compose, contoh DB_HOST: db
  • depends_on tapi web tetap error karena db belum siap → depends_on hanya menjamin urutan start, bukan kesiapan database. image MySQL butuh beberapa detik sampai siap menerima koneksi. solusi cepat: tambahkan mekanisme retry di aplikasi, atau healthcheck
  • port bentrok → kalau port 5000 di host sudah dipakai aplikasi lain, ganti sisi kiri pemetaan, misal "5001:5000"
  • perubahan kode tidak muncul setelah rebuild → jalankan docker compose build dulu lalu docker compose up -d, karena up saja tidak otomatis membangun ulang image yang sudah ada
  • data hilang setelah docker compose down → itu normal kalau tidak pakai volume. tambahkan bagian volumes seperti contoh di atas, dan hindari down -v kalau mau menyimpan data

Kesimpulan

Dockerfile adalah resep membangun image dengan instruksi FROM, WORKDIR, COPY, RUN, EXPOSE, dan CMD, sedangkan multi-stage build memisahkan proses build dan produksi supaya image akhir kecil dan aman. Docker Compose merangkai banyak service dalam satu file YAML, menyatukan aplikasi, database, volume, dan jaringan, lalu menjalankan semuanya dengan docker compose up -d.

urutan belajarnya jelas: pahami konsep container dulu, latih Dockerfile sampai lancar, lalu gabungkan semuanya lewat Compose. begitu dua fondasi ini di luar kepala, jalan ke deployment, CI/CD, dan Kubernetes jadi jauh lebih mulus.

Baca Juga Mengenai :

Pertanyaan yang Sering Diajukan

Apa bedanya Dockerfile dan Docker Compose?

Dockerfile adalah resep untuk membangun satu image, berisi instruksi FROM, COPY, RUN, dan CMD. Docker Compose adalah file YAML untuk menjalankan banyak container sekaligus sebagai satu kesatuan, misalnya aplikasi plus database. Dockerfile membangun image, Compose merangkai container.

Apakah version di docker-compose.yml masih diperlukan?

Tidak. sejak Docker Compose v2 yang jalan dengan perintah docker compose, baris version sudah tidak dipakai lagi dan diabaikan. Baris version hanya relevan untuk tool lama docker-compose versi 1.x. Tinggalkan saja versi-nya, Compose modern otomatis memakai spesifikasi terbaru.

Apa bedanya docker compose dan docker-compose?

docker compose tanpa tanda hubung adalah perintah bawaan Docker versi baru yang direkomendasikan. docker-compose dengan tanda hubung adalah tool Python lama yang sekarang sudah deprecated. Keduanya membaca file yang sama, tapi gunakan docker compose.

Apa itu multi-stage build dan kenapa dipakai?

Multi-stage build memisahkan proses build dan hasil akhir ke dalam beberapa stage dalam satu Dockerfile. Stage pertama dipakai mengompilasi kode dengan dependency lengkap, stage kedua hanya mengambil hasilnya. Hasilnya image produksi jauh lebih kecil dan aman karena tidak membawa tool build.

Kenapa data di database hilang setelah container dihapus?

Container bersifat sementara, semua data yang tidak disimpan di volume akan hilang saat container dihapus. Solusinya pasang volume di docker-compose.yml, contoh db_data:/var/lib/mysql. Data lalu disimpan di volume yang bertahan walau container dibuang.

Apakah Docker wajib dipelajari developer aplikasi biasa?

Sangat disarankan. Docker menyelesaikan masalah paling klasik, "di komputer saya jalan" dengan menyamakan lingkungan antara development, staging, dan production. Sekali bisa menulis Dockerfile dan docker-compose.yml, onboarding anggota tim baru atau deploy ke server jadi jauh lebih sederhana.

Sigit Nurhanafi avatar
Full-stack developer & technical writer. Berpengalaman di PHP, Laravel, NodeJS, MySQL, dan Python. Aktif menulis tutorial pemrograman dan maintaining open-source projects di PemburuKode sejak 2021.