Kalimat “di komputer saya jalan” adalah pengakuan kegagalan deployment. Aplikasi Python bergantung pada versi interpreter, paket sistem, dan puluhan dependencies — kombinasi yang mudah berbeda antara laptop dan server. Docker menyelesaikannya dengan mengemas semuanya menjadi satu image yang identik di mana pun dijalankan. Pada chapter ini kita menyusun Dockerfile untuk aplikasi Python yang layak produksi, dan memahami setiap keputusan di dalamnya.

Konsep Inti: Image, Container, dan Layer #

Tiga istilah ini adalah fondasi Docker:

IstilahAnalogiPeran
Imagecetakan atau resepberkas read-only berisi kode, interpreter, dan dependencies
Containerproses hasil cetakinstance berjalan dari sebuah image
Layerlapisan kuesetiap instruksi Dockerfile menghasilkan lapisan yang di-cache

Sistem layer inilah yang membuat Docker ramah bagi Python: instruksi yang tidak berubah tidak dijalankan ulang. Menyalin pyproject.toml lebih dulu, memasang dependencies, dan baru menyalin kode membuat dependency install terjadi sekali — bukan pada setiap perubahan baris kode.

Dockerfile untuk Aplikasi Python #

Berikut Dockerfile untuk project toko-api dengan src layout dari awal section ini:

dockerfile
FROM python:3.12-slim

WORKDIR /app

# Layer 1: dependensi — jarang berubah
COPY pyproject.toml README.md ./
RUN pip install --no-cache-dir "fastapi>=0.110,<1" "uvicorn[standard]>=0.29,<1"

# Layer 2: kode aplikasi — berubah di setiap commit
COPY src/ ./src/
RUN pip install --no-cache-dir .

# User non-root
RUN useradd --create-home appuser
USER appuser

EXPOSE 8000
CMD ["uvicorn", "toko_api.main:app", "--host", "0.0.0.0", "--port", "8000"]

Mari kita telaah baris per baris:

  1. Baris 1 (FROM python:3.12-slim): image dasar resmi Python versi 3.12 ragam slim — cukup lengkap untuk aplikasi web, jauh lebih kecil daripada image penuh.
  2. Baris 3 (WORKDIR /app): mengatur folder kerja container; instruksi COPY berikutnya relatif ke sini.
  3. Baris 6 (COPY pyproject.toml README.md ./): menyalin hanya berkas metadata yang dibutuhkan instalasi dependencies — README.md ikut karena dirujuk kunci readme di pyproject.toml.
  4. Baris 7 (RUN pip install ...): memasang dependencies aplikasi secara eksplisit. Layer ini hanya dibangun ulang bila kedua berkas di baris 6 berubah.
  5. Baris 10-11 (COPY src/, pip install .): kode disalin terakhir, lalu aplikasi dipasang sebagai package — entry points ikut tersedia di dalam image.
  6. Baris 14-15 (useradd, USER appuser): container tidak lagi berjalan sebagai root; bila aplikasi diretas, penyerang tidak serta-merta memperoleh hak penuh di dalam container.
  7. Baris 17 (EXPOSE 8000): dokumentasi port yang dipakai aplikasi.
  8. Baris 18 (CMD [...]): perintah default saat container start — format list exec form lebih aman karena langsung dieksekusi tanpa shell perantara.

Perbedaan CMD dan ENTRYPOINT singkatnya: CMD adalah perintah yang mudah diganti saat docker run (misalnya diganti bash untuk mengeksplorasi image), sedangkan ENTRYPOINT menjadikan container berperilaku seperti executable tetap. Aplikasi web cukup CMD; wrapper tool seperti docker run --rm mycli --help memanfaatkan ENTRYPOINT.

.dockerignore #

Sama seperti .gitignore, berkas .dockerignore di akar project mencegah berkas sampah ikut terkirim ke daemon Docker:

text
.git
.venv
__pycache__/
*.pyc
.env
dist/
build/
.pytest_cache/
tests/

Entri .env penting: COPY src/ ./src/ tidak menyalinnya, tetapi berkas .env yang bocor ke image adalah kebocoran secret klasik. Image yang lebih kecil juga berarti build dan pull lebih cepat.

Membangun dan Menjalankan #

Docker tidak tersedia di lingkungan pengujian materi ini, sehingga seluruh transkrip pada bagian ini bersifat ilustratif — perintah dan output mengikuti dokumentasi resmi Docker.

bash
docker build -t toko-api:1.4.0 .

Output yang Dihasilkan (ilustratif — Docker tidak tersedia di lingkungan pengujian materi ini):

text
[+] Building 12.4s (11/11) FINISHED
 => [internal] load build definition from Dockerfile
 => [1/5] FROM python:3.12-slim
 => [2/5] WORKDIR /app
 => [3/5] COPY pyproject.toml README.md ./
 => [4/5] RUN pip install --no-cache-dir "fastapi>=0.110,<1" "uvicorn[standard]>=0.29,<1"
 => [5/5] COPY src/ ./src/
 => exporting to image
 => => naming to docker.io/library/toko-api:1.4.0

Menjalankan container dengan port dipetakan dan environment dari berkas .env:

bash
docker run --rm -p 8000:8000 --env-file .env toko-api:1.4.0

Output yang Dihasilkan (ilustratif):

text
INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

Flag --rm membersihkan container setelah berhenti, -p 8000:8000 memetakan port host ke port container, dan --env-file menyuntikkan konfigurasi tanpa membakarnya ke image. Untuk aplikasi beserta database-nya, docker compose merangkai kedua container:

yaml
services:
  web:
    build: .
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - db
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: ${TOKO_DB_PASSWORD}

Satu perintah docker compose up menjalankan aplikasi dan database secara bersamaan — environment pengembangan yang dapat direplikasi seluruh tim.

Ukuran Image dan Multi-Stage #

Image yang ramping mempercepat deployment dan menghemat biaya penyimpanan. Tiga kebiasaan yang paling berdampak: pakai base image slim; selalu --no-cache-dir agar pip tidak menyimpan arsip di dalam image; dan jangan pasang tool build di image final. Teknik multi-stage build mewujudkan ketiganya — tahap pertama (builder) memasang compiler dan membangun semua wheel; tahap final hanya menyalin hasilnya:

dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml README.md ./
RUN pip wheel --wheel-dir /wheels "fastapi>=0.110,<1" "uvicorn[standard]>=0.29,<1"

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
COPY src/ ./src/
RUN pip install --no-cache-dir .
CMD ["uvicorn", "toko_api.main:app", "--host", "0.0.0.0", "--port", "8000"]

Berhati-hatilah dengan base image alpine untuk Python: compiler musl-nya memaksa banyak paket dibangun dari sumber, sering kali lebih lambat dan lebih rawan galat daripada slim. Terakhir, tool modern seperti uv dan Poetry bisa menggantikan pip di dalam Dockerfile — uv khususnya populer karena instalasi dependencies-nya jauh lebih cepat dan cache-nya bisa dimanfaatkan antar-build.

Container menuntut satu disiplin yang sudah kita siapkan: konfigurasi harus datang dari environment — fokus chapter berikutnya.