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-slimmenentukan base image.slimartinya versi ramping Debian, jauh lebih kecil dari versi penuh. semua instruksi berikutnya dibangun di atas image iniWORKDIR /appmembuat folder kerja/appdi dalam container dan memindahkan posisi kerja ke sana. kalau folder tidak ada, otomatis dibuat. instruksi setelahnya berjalan di folder iniCOPY requirements.txt .menyalin filerequirements.txtdari komputer kalian ke dalam folder kerja di imageRUN pip install -r requirements.txtmenjalankan perintah saat proses build, hasilnya terekam permanen di imageCOPY . .menyalin seluruh isi project ke dalam imageEXPOSE 5000mendeklarasikan bahwa aplikasi memakai port 5000, ini petunjuk dokumentasi, port belum terbuka ke host sebelum dipetakan saat runCMD ["python", "app.py"]menentukan perintah yang dijalankan saat container mulai. bedanya denganRUN:RUNjalan saat build,CMDjalan 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:alpineyang ramping, laluCOPY --from=buildermengambil hanya folder/app/disthasil 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:
servicesberisi daftar container yang akan dijalankan, di sini ada dua:webdandbweb.build: .menyuruh Compose membangun image servicewebdari Dockerfile di folder saat ini. alternatifnyaimage: nama-imagekalau memakai image jadi dari registryportsmemetakan port host ke container,"5000:5000"artinya port 5000 di komputer kalian menuju port 5000 di containerdepends_on: [db]memastikan containerdbdinyalakan lebih dulu sebelumwebdb.image: mysql:8.0service ini memakai image MySQL resmi tanpa build sendirienvironmentmengisi variable yang dibaca image mysql untuk set password root dan nama database awalvolumes: db_data:/var/lib/mysqlmenyimpan data MySQL di volume bernamadb_datasupaya tidak hilang saat container dihapusvolumes: 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, contohDB_HOST: dbdepends_ontapi web tetap error karena db belum siap →depends_onhanya 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 builddulu laludocker compose up -d, karenaupsaja tidak otomatis membangun ulang image yang sudah ada - data hilang setelah
docker compose down→ itu normal kalau tidak pakai volume. tambahkan bagianvolumesseperti contoh di atas, dan hindaridown -vkalau 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.

