SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Docker Compose: Perbedaan build dan image di VPS

Pahami perbedaan build dan image di Docker Compose: image menarik tag registry, sedangkan build membuat image lokal. Temukan alasan perubahan Dockerfile diabaikan dan solusinya.

Docker Compose: perbedaan build dan image secara singkat

Dalam file Docker Compose, image: menentukan image yang akan diambil dari registry, sedangkan build: memberi tahu Compose untuk membangun image di mesin ini berdasarkan Dockerfile. Jika hanya image: yang ditetapkan, Compose mengambil tag tersebut lalu menjalankannya. Jika hanya build: yang ditetapkan, Compose membangun image di sini dan memberinya nama yang diturunkan dari nama project serta nama service. Jika keduanya ditetapkan, Compose membangun image secara lokal, lalu memberi tag pada hasilnya menggunakan nama dalam image:. Dengan cara ini, Anda dapat membangun image lalu mengunggahnya dengan nama yang dipilih.

Itulah seluruh perbedaannya. Bagian berikut menjelaskan dampaknya saat dijalankan di server. Tutorial ini mengasumsikan Docker Engine dan plugin Compose sudah terpasang; menjalankan Docker pada VPS membahas bagian tersebut.

Tiga bentuk lengkap

Tarik tag yang telah dipublikasikan lalu jalankan. Dockerfile tidak digunakan pada tahap apa pun.

services:
  web:
    image: nginx:1.27
    restart: unless-stopped
    ports:
      - "80:80"

Build dari Dockerfile di direktori saat ini. Tidak ada yang ditarik selain base image yang disebutkan dalam FROM.

services:
  web:
    build: .
    restart: unless-stopped
    ports:
      - "80:80"

Build secara lokal lalu beri tag pada hasilnya. docker compose push kemudian dapat mengirim tag tersebut ke registry.

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: registry.example.com/acme/web:1.4.2
    restart: unless-stopped
    ports:
      - "80:80"

context adalah direktori yang dikirim ke builder. dockerfile di-resolve secara relatif terhadap context tersebut, sehingga context: . dengan dockerfile: docker/prod.Dockerfile merupakan hal yang normal dan benar. Jalankan docker compose images untuk melihat nama image dan image ID di balik setiap service container. Ini adalah cara tercepat untuk memastikan bentuk mana dari ketiga bentuk tersebut yang benar-benar Anda tulis.

Mengapa docker compose up tidak melakukan build ulang setelah saya mengubah Dockerfile?

Karena up memeriksa apakah image ada, bukan apakah image tersebut masih terbaru.

Saat Compose menjalankan service yang memiliki bagian build:, Compose mencari image tersebut di penyimpanan image lokal. Jika image dengan nama itu sudah ada, Compose menggunakannya. Compose tidak membaca Dockerfile, membandingkan file sumber, atau memeriksa timestamp. Spesifikasi Compose menetapkan aturan ini melalui atribut pull_policy, dan perilaku default hanya melakukan build image jika image tersebut belum ada. Image yang sudah ada dianggap cukup.

Jadi, Anda mengedit app.py, menjalankan docker compose up -d, melihat Compose melaporkan container sebagai running, tetapi aplikasi tetap menjalankan kode lama. Tidak ada yang gagal, sehingga tidak ada peringatan. Ini adalah laporan paling umum tentang perubahan yang tidak diterapkan saat menggunakan Compose. Petunjuknya adalah kata status yang dicetak Compose di sebelah nama container: container yang diganti oleh Compose dilaporkan sebagai recreated atau started, sedangkan container yang dibiarkan oleh Compose dilaporkan sebagai running.

Dua pemeriksaan dapat memastikannya. docker compose images menampilkan image ID yang digunakan setiap container. Catat nilainya sebelum deployment, lalu bandingkan setelahnya. docker image ls memiliki kolom CREATED. Image yang dibuat sebelum commit terakhir Anda tetap merupakan image usang, apa pun yang dicetak oleh skrip deployment.

Flag yang memaksa build ulang

  • docker compose up -d --build melakukan build terlebih dahulu, lalu membuat ulang setiap container yang image-nya berubah. Inilah flag yang paling sering dicari.
  • docker compose build web melakukan build pada satu service dan tidak menjalankan apa pun. Gunakan docker compose up --no-deps -d web setelahnya untuk mengganti hanya container tersebut dan membiarkan stack lainnya tetap berjalan.
  • docker compose build --no-cache web membuang semua layer yang tersimpan dalam cache dan melakukan build ulang sejak instruksi pertama.
  • docker compose build --pull mencoba menarik versi terbaru base image dalam FROM. Dengan demikian, tag yang dapat berubah seperti node:22 akan menggunakan isi terbarunya, bukan salinan yang Anda unduh pada Maret.
  • docker compose up -d --force-recreate membuat ulang container dari image yang sudah digunakannya. Flag ini tidak pernah melakukan build. Menggunakannya saat yang dimaksud adalah --build merupakan kesalahan yang sering berakhir tanpa hasil.

Anda juga dapat memindahkan keputusan ini ke dalam file. Menurut spesifikasi Compose, pull_policy: build berarti Compose melakukan build pada image dan melakukan build ulang jika image tersebut sudah ada. Setiap up kemudian selalu menjalankan build, yang sesuai untuk laptop tetapi jarang sesuai untuk server.

services:
  web:
    build: .
    image: registry.example.com/acme/web:dev
    pull_policy: build

Ada satu interaksi lain yang perlu diketahui. docker compose pull juga mencoba menarik image untuk service yang memiliki bagian build. Jika proses pull tersebut gagal, Compose memberi tahu bahwa image harus di-build. Gunakan --ignore-buildable agar service tersebut dilewati tanpa pesan.

Cara build cache menentukan waktu deploy

Setiap instruksi dalam Dockerfile menghasilkan sebuah layer, dan builder menggunakan kembali layer dari cache jika instruksi tersebut beserta inputnya tidak berubah. Untuk COPY, inputnya adalah isi file yang disalin. Setelah salah satu layer tidak ditemukan di cache, semua layer setelahnya dibangun ulang karena setiap layer dibangun berdasarkan filesystem yang dihasilkan layer sebelumnya.

Aturan ini menentukan apakah deploy Anda selesai dalam hitungan detik atau menit. Urutkan Dockerfile mulai dari bagian yang paling jarang berubah hingga bagian yang berubah pada setiap commit.

FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]

npm ci berada di atas COPY . ., sehingga pengeditan file sumber membuat install layer tetap tersedia di cache dan proses build dilanjutkan dari langkah penyalinan. Jika kedua baris tersebut ditukar, perubahan satu karakter akan menginstal ulang semua dependensi karena COPY . . membatalkan layer yang menjadi dasar npm ci. Pola yang sama berlaku untuk pip install -r requirements.txt dan go mod download.

--no-cache adalah alat yang tepat jika Anda menduga layer lama menyembunyikan perbaikan yang baru dibuat. Namun, jangan menjadikannya pilihan default karena perintah tersebut membuang penggunaan ulang cache yang ingin dicapai melalui pengurutan Dockerfile.

Satu hal dapat ditetapkan oleh image dan ditimpa oleh Compose: CMD dalam Dockerfile menentukan perintah yang dijalankan image secara default, sedangkan key command: pada service menggantikannya. Cara command dan entrypoint berinteraksi penting dalam konteks ini karena override Compose dapat membuat image yang baru dibangun berperilaku persis seperti image lama.

Konteks build dan .dockerignore

context: . membuat paket dari direktori tersebut dan mengirimkannya ke builder sebelum instruksi pertama dijalankan. Semua isi direktori ikut dikirim, termasuk .git dan direktori data apa pun yang Anda simpan di samping source. Jika build berhenti pada tahap transfer konteks pada project yang tidak berubah, berarti konteks tersebut terlalu besar.

File .dockerignore di root konteks mengecualikan path tertentu dari transfer tersebut. Sintaksnya mirip dengan .gitignore.

.git
node_modules
*.log
data/
.env

Ada dua manfaatnya. Transfer menjadi lebih kecil sehingga setiap build dimulai lebih cepat. Selain itu, COPY . . tidak dapat lagi menyalin .env ke dalam image, tempat siapa pun yang mengambil image tersebut dapat membacanya kembali.

Kasus build lambat yang membesar seiring waktu biasanya disebabkan oleh bind mount. Named volume berada di luar direktori project, tetapi bind mount seperti ./data:/var/lib/postgresql/data berada di dalam konteks build. Akibatnya, build menjadi semakin lambat setiap minggu seiring bertambahnya ukuran database. Satu baris di .dockerignore dapat mengatasinya. Bind mount dan named volume membahas trade-off yang lebih luas.

Build argument memiliki risiko serupa dalam skala yang lebih kecil. Nilai yang diteruskan melalui args: terlihat dalam image history oleh siapa pun yang memiliki image tersebut. Karena itu, masukkan nomor versi di sana dan jangan pernah memasukkan token. File env dan secret di Compose membahas tempat yang tepat untuk menyimpan kredensial.

Sebaiknya melakukan build di VPS atau di tempat lain lalu melakukan pull?

Melakukan build pada server yang melayani trafik Anda adalah pilihan default karena alurnya paling singkat: git pull, lalu docker compose up -d --build. Ini tidak masalah pada server kecil yang belum digunakan oleh siapa pun. Namun, pendekatan ini tidak lagi tepat karena dua alasan yang dapat diukur dan satu alasan yang hanya terlihat pada hari yang buruk.

Memori. Build menjalankan compiler dan bundler berdampingan dengan aplikasi yang sedang aktif, dan keduanya biasanya merupakan bagian yang paling banyak menggunakan memori dalam sebagian besar stack. Pada VPS 1 GB, bundler JavaScript atau proses compile Rust biasanya menjadi proses terbesar di server. Ketika kernel kehabisan memori, kernel menghentikan proses terbesar: build dapat berhenti dengan Killed dan exit status 137, atau database yang dihentikan sehingga situs tidak aktif di tengah deploy. dmesg -T | grep -i oom menampilkan baris penghentian beserta nama prosesnya. Dengan begitu, Anda dapat mengetahui proses mana yang dihentikan tanpa menebak.

Disk. Setiap build meninggalkan layer, dan builder menyimpan cache-nya sendiri secara terpisah dari image Anda. docker system df menampilkan keduanya, dan baris build cache terus bertambah. Bersihkan dengan docker image prune untuk dangling image dan docker builder prune untuk cached layer. Disk yang penuh menghentikan lebih dari sekadar build. Database juga berhenti menulis, dan kegagalan ini menimbulkan dampak yang jauh lebih besar daripada deploy yang lambat.

Reproducibility. Image yang dibuat di server hanya tersedia di server tersebut. Rollback berarti melakukan checkout commit lama lalu melakukan build lagi. Build tersebut belum tentu menghasilkan image yang sama karena base tag dan package mirror dapat berubah. Melakukan build di tempat lain lalu melakukan push terhadap tag mengubah rollback menjadi pengeditan: arahkan image: ke tag sebelumnya lalu jalankan docker compose up -d.

Susunan yang dapat diandalkan cukup sederhana. Continuous integration Anda menjalankan build dan melakukan push terhadap registry.example.com/acme/web:<git-sha>. File Compose pada VPS menyertakan image: tanpa key build: sama sekali. Dengan begitu, deployment hanya memerlukan dua command dan hampir tidak menggunakan memori.

docker compose pull
docker compose up -d

Jalankan docker login registry.example.com satu kali di server. Setelah itu, Compose dapat melakukan pull terhadap private tag.

Pertahankan bagian build untuk development. Jangan menghapusnya. Simpan bagian tersebut dalam file dengan nama yang Anda tentukan sendiri.

# compose.dev.yaml
services:
  web:
    build:
      context: .
    pull_policy: build
docker compose -f compose.yaml -f compose.dev.yaml up -d --build

Beri nama file tersebut compose.dev.yaml, bukan compose.override.yaml. Compose memuat file override secara otomatis jika file tersebut tersedia. Karena itu, file override yang tidak sengaja disalin ke server dapat membuat proses build berjalan lagi secara diam-diam di sana. Pelapisan beberapa file Compose menjelaskan cara merge menyelesaikan setiap key.

Jebakan arsitektur saat Anda melakukan build di tempat lain

Sebuah image membawa arsitektur CPU yang digunakan saat image tersebut dibuat. Jika Anda melakukan build pada laptop Apple Silicon, melakukan push, lalu melakukan pull tag tersebut ke VPS x86_64, Docker memperingatkan bahwa platform image yang diminta tidak cocok dengan platform host yang terdeteksi. Proses kemudian berhenti dengan exec format error. Pesan ini terlihat seperti menunjukkan binary yang rusak, tetapi sebenarnya bukan. Tentukan target build secara eksplisit:

docker buildx build --platform linux/amd64 \
  -t registry.example.com/acme/web:1.4.2 --push .

Ketidakcocokan yang sama terjadi secara terbalik jika laptop Anda menggunakan x86 dan Anda menjalankan VPS ARM, bukan VPS x86. Biarkan CI melakukan build pada arsitektur yang digunakan saat deployment agar masalah ini tidak perlu dipertimbangkan.

Hal yang perlu diperiksa setelah deployment

  • docker compose images menampilkan image dan tag yang digunakan setiap container yang sedang berjalan. ID image yang berubah membuktikan bahwa build baru sudah digunakan.
  • docker compose config menampilkan file hasil penggabungan setelah substitusi variabel. Dengan demikian, Anda dapat melihat nama image final yang akan digunakan Compose sebelum menjalankan apa pun.
  • docker compose logs -f web selama 30 detik pertama setelah pergantian. Container yang start lalu berhenti akan restart dalam loop, bukan tetap berjalan. Loop ini tidak terlihat kecuali Anda memeriksanya.
  • docker image ls menampilkan kolom CREATED. Image yang lebih lama daripada commit terakhir berarti belum pernah dibangun ulang.

Jika Anda masih menyusun file yang menjadi dasar pemeriksaan ini, dasar-dasar file Compose pada VPS membahas key terkait, sedangkan lembar contekan perintah Compose mencantumkan subperintah lainnya.

FAQ

Dapatkah saya menggunakan build dan image pada service yang sama?

Ya, dan ini merupakan pengaturan yang umum untuk project yang Anda build sendiri. Compose melakukan build dari bagian build: dan memberi tag pada hasilnya dengan nilai image:. Tag tersebut digunakan docker compose push untuk mengirim image ke registry dan digunakan mesin lain untuk melakukan pull. Tanpa key image:, Compose tetap melakukan build, tetapi menamai image berdasarkan project dan service, serta memperingatkan bahwa atribut yang tidak ada tersebut mencegah image di-push.

Mengapa docker compose up tidak menerapkan perubahan pada Dockerfile saya?

Karena up hanya memeriksa apakah image dengan nama tersebut ada. Jika image tersedia, Compose menjalankannya dan tidak pernah membandingkannya dengan Dockerfile atau file sumber Anda. Jalankan docker compose up -d --build, atau jalankan docker compose build web lalu docker compose up --no-deps -d web untuk mengganti satu service. Menetapkan pull_policy: build pada service membuat setiap up melakukan build ulang, sehingga sesuai untuk mesin development.

Apa perbedaan antara --build dan --force-recreate?

--build melakukan build ulang pada image, lalu membuat ulang container yang imagenya berubah. --force-recreate membuat ulang container dari image yang sudah dimilikinya, sehingga tidak akan pernah menerapkan perubahan kode. Jika perubahan Anda terdapat pada source atau Dockerfile, --build adalah flag yang diperlukan. --force-recreate digunakan untuk mereset container itu sendiri, misalnya untuk menghapus writable layer-nya sambil mempertahankan image yang sama.

Sebaiknya saya melakukan build Docker image di VPS atau di tempat lain?

Lakukan build di tempat lain, lalu pull tag setelah server juga melayani trafik. Proses build bersaing dengan aplikasi untuk mendapatkan memory. Pada VPS kecil, persaingan ini dapat membuat kernel menghentikan proses terbesar, yang mungkin merupakan proses build atau database Anda. Proses build juga meninggalkan cache pada disk yang tidak dibersihkan secara otomatis. Melakukan build di server tetap sesuai untuk project kecil tanpa pengguna. Pemindahan di kemudian hari juga mudah jika Anda menyimpan bagian build: dalam file Compose khusus development.

Bagaimana cara mencegah cache Docker build memenuhi disk?

Jalankan docker system df untuk melihat kapasitas yang digunakan image dan build cache Anda. docker builder prune menghapus layer yang tersimpan dalam cache, sedangkan docker image prune menghapus dangling image yang ditinggalkan oleh proses build sebelumnya. Menambahkan -a pada salah satunya akan melakukan pembersihan yang lebih agresif dan memaksa build berikutnya dimulai tanpa cache. Jangan menjadwalkan docker system prune -af --volumes pada server, karena --volumes menghapus volume yang sedang tidak digunakan oleh container mana pun. Stack yang dihentikan untuk pemeliharaan menyimpan database tepat di volume seperti itu.