Pembangunan Multistage Docker Kurangi Ukuran Gambar Tanpa Mengganggu Deploy
Panduan praktis tentang pembangunan Docker berbagai tahap — ukuran gambar sebelum dan sesudah yang nyata, contoh Dockerfile untuk Node.js dan Python, serta kesalahan yang sering terjadi pada setiap tim setidaknya sekali.
Gambar Node.js Anda berukuran 1,1 GB. Anda telah menambahkan .dockerignore, memangkas ketergantungan pengembangan, mencoba node:slim — tidak berubah banyak. Solusi sebenarnya adalah pembangunan multistage. Jika Anda belum beralih, Anda mengirimkan kompilator TypeScript ke produksi.
Pembangunan multistage telah ada dalam Docker sejak versi 17.05 (2017). Mereka hanya kurang digunakan. Berikut penjelasan nyata: apa yang berubah, seberapa besar perbedaannya, dan tiga kegagalan yang menyerang tim saat pertama kali melakukan migrasi.
Masalah satu tahap
Sebagian besar Dockerfile dimulai seperti ini:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/server.js"]
Bangun dan periksa dengan docker images: ~1,1 GB. Anda mengirimkan gambar Node 20 penuh dengan npm, rantai alat TypeScript Anda, semua ketergantungan pengembangan, dan pohon sumber kode Anda. Tidak satupun dari itu berjalan di produksi — aplikasi hanya membutuhkan output yang dikompilasi dan sejumlah kecil paket runtime. dist/ .
Pembangunan multistage: solusi
Setiap FROM instruksi memulai tahap baru dengan sistem file yang bersih. Beri nama tahap dengan AS, lalu gunakan COPY --from=stagename untuk mengambil file tertentu ke tahap berikutnya. Tahap-tahap antara tidak masuk ke gambar akhir — mereka adalah hasil pembangunan, yang dibuang setelah COPY selesai.
Berikut aplikasi yang sama dalam pembangunan multistage yang benar:
# ---- Build stage ----
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- Runtime stage ----
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
Baris kritis: COPY --from=builder /app/dist ./dist. Ini mengambil hanya output yang dikompilasi dari tahap pembangun ke gambar runtime berbasis Alpine. Kompilator TypeScript, file sumber, dan ketergantungan pengembangan tidak pernah menyentuh lapisan akhir.
Hasil: ~160 MB sebagai ganti 1,1 GB. Itu merupakan penurunan sekitar 85% untuk aplikasi Node biasa — dan itu adalah hasil bangunan yang sama, hanya tanpa struktur pendukungnya.
Menambahkan tahap uji
Anda dapat menambahkan tahap uji antara pembangunan dan runtime yang menjalankan suite uji Anda. Jika uji gagal, pembangunan berhenti sebelum gambar runtime dibuat. Jika uji berhasil, Anda melewatkan tahap uji saat membangun untuk produksi.
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM builder AS tester
RUN npm test
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
Di CI Anda menargetkan tahap tester secara eksplisit: docker build --target tester .. Untuk gambar produksi Anda membangun tanpa target dan Docker menjalankan semua tahap secara berurutan, berhenti pada tahap terakhir FROM. Tahap uji berjalan tetapi sistem file-nya dibuang — uji berfungsi sebagai pintu, bukan sebagai beban.
Python: ide yang sama, eksekusi yang sedikit berbeda
Pembangunan multistage untuk Python mengikuti pola yang sama. Perbedaan utama: instalasi paket pip terjadi di bawah /root/.local ketika Anda menggunakan --user, sehingga Anda menyalin direktori tersebut ke gambar runtime yang ringkas.
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "main.py"]
python:3.12 basis: ~1 GB. python:3.12-slim dengan hanya paket yang terinstal: ~180–250 MB tergantung pada isi requirements.txt. File yang dikompilasi datang secara gratis karena mereka berada di samping sumber. .pyc .
Tiga kegagalan yang menangkap setiap orang setidaknya sekali
1. Menyalin file yang salah
Kesalahan paling umum: Anda COPY --from=builder /app ./ alih-alih COPY --from=builder /app/dist ./dist. Anda hanya menyalin semuanya — file sumber, fixture uji, node_modules, dan lainnya — ke gambar runtime "minimal" Anda. Sekarang ukurannya lebih besar daripada versi satu tahap.
Jangan ambigu tentang apa yang Anda salin. Salin hanya direktori atau file yang benar-benar dibutuhkan oleh titik masuk produksi. Untuk aplikasi Node kebanyakan: output yang dikompilasi (dist/) dan opsional aset statis. Untuk Python: paket yang terinstal dan kode aplikasi, bukan requirements.txt, bukan uji, bukan notebook.
2. Rahasia pembangunan terbocor ke lapisan
Jika Anda mengirimkan rahasia sebagai argumen pembangunan (misalnya, ARG NPM_TOKEN diikuti dengan penggunaannya dalam perintah RUN ), rahasia itu terlihat di setiap lapisan yang mengikuti — bahkan dalam pembangunan multistage. docker history myimage akan menunjukkan hal itu.
Pendekatan yang benar adalah Docker BuildKit's --mount=type=secret:
RUN --mount=type=secret,id=npm_token NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
Rahasia dimount saat runtime lapisan tersebut — tidak pernah disimpan dalam sejarah gambar. Bangun dengan: docker build --secret id=npm_token,src=.npmrc .
Solusi cepat yang digunakan orang — menghapus rahasia di lapisan yang sama RUN — tidak membantu dalam pembangunan multistage, tetapi pendekatan BuildKit lebih bersih terlepas dari itu.
3. Lupa .dockerignore
Pembangunan multistage memperkecil gambar akhir Anda, tetapi COPY . . di tahap pembangunan masih mengirimkan konteks penuh ke daemon Docker. Tanpa .dockerignore, itu termasuk .git/, node_modules/, fixture uji, file lokal .env , dan rahasia yang disimpan dalam file teks biasa. Tahap pembangunan melihat semuanya.
Minimum .dockerignore untuk proyek Node apa pun:
.git
node_modules
dist
.env
*.log
coverage
.nyc_output
Tambahkan .dockerignore hari Anda menambahkan Dockerfile. Ukuran konteks pembangunan muncul di baris pertama dari docker build output (Sending build context to Docker daemon X MB) — jika angka itu terlalu besar, periksa apa yang termasuk.
Alat yang berguna untuk pekerjaan Dockerfile
Jika Anda ingin titik awal sebelum menulis sendiri, Generator Dockerfile di IO Tools akan mengatur Dockerfile multistage untuk tumpukan umum. Setelah Anda memiliki sesuatu yang ditulis, jalankan melalui Pemformat & Pemeriksa Linter Dockerfile untuk menangkap kesalahan umum sebelum mereka mencapai CI — seperti kehilangan WORKDIR, menggunakan latest tag, atau menjalankan sebagai root secara tidak perlu.
Pesan akhir
Pembangunan multistage adalah perubahan dua langkah: tambahkan tahap pembangunan yang dinamakan, salin output yang dikompilasi ke gambar minimal baru. Penurunan ukuran hampir selalu layak — 80–90% adalah nilai biasa untuk aplikasi Node dan Python. Kegagalan utama adalah terlalu luas dengan COPY --from, mengungkapkan rahasia sebagai argumen pembangunan, dan melewatkan .dockerignore. Perbaiki hal ini dan Anda memiliki gambar produksi yang benar-benar disesuaikan untuk produksi.
Instal Ekstensi Kami
Tambahkan alat IO ke browser favorit Anda untuk akses instan dan pencarian lebih cepat
恵 Papan Skor Telah Tiba!
Papan Skor adalah cara yang menyenangkan untuk melacak permainan Anda, semua data disimpan di browser Anda. Lebih banyak fitur akan segera hadir!
Alat Wajib Coba
Lihat semua Pendatang baru
Lihat semuaMemperbarui: Kita alat terbaru ditambahkan pada 13 Juni 2026
