Container yang sudah berjalan stabil di production bukan akhir dari pekerjaan Sysadmin/DevOps Engineer, justru di titik itulah pekerjaan operasional sehari-hari dimulai. Image aplikasi perlu diperbarui tanpa mengganggu pengguna yang sedang aktif, data di dalam volume perlu dicadangkan supaya tidak hilang kalau terjadi insiden, pemakaian CPU dan memory perlu dijaga supaya satu container nakal tidak menghabiskan resource container lain, dan seluruh aktivitas di level Docker daemon perlu terpantau supaya masalah bisa terdeteksi sebelum berubah jadi insiden besar. Bab ini membahas praktik operasi dan pemeliharaan harian tersebut, mulai dari strategi update container, backup dan recovery data, pengaturan resource, sampai pemantauan infrastruktur Docker itu sendiri.
43.1 Strategi Update Container Tanpa Downtime
Mengganti versi image yang sedang berjalan di production butuh kehati-hatian ekstra, karena kesalahan kecil bisa membuat layanan mendadak tidak bisa diakses pengguna. Sub-bab ini membahas cara memperbarui container di satu host Docker biasa, di luar orkestrator seperti Docker Swarm atau Kubernetes yang sudah punya mekanisme rolling update bawaan.
43.1.1 Update Image dengan Docker Compose
Untuk aplikasi yang dijalankan lewat docker compose, alur update paling umum adalah menarik image baru lalu meminta Compose membuat ulang container yang memakainya. Siapkan dulu contoh docker-compose.yml sederhana berikut sebagai bahan praktik.
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
restart: unless-stoppedJalankan container ini terlebih dahulu, lalu simulasikan proses update ke tag versi baru.
docker compose up -d
docker compose pull
docker compose up -dPerintah docker compose pull menarik image terbaru sesuai tag yang tertulis di docker-compose.yml, sedangkan docker compose up -d yang kedua membandingkan konfigurasi container yang berjalan dengan image yang baru ditarik; kalau ada perbedaan, Compose menghentikan container lama dan membuat container baru dari image tersebut. Proses ini tetap menimbulkan downtime singkat, karena container lama dihentikan dulu sebelum container baru siap menerima traffic. Untuk layanan yang benar-benar tidak boleh mengalami downtime sama sekali, strategi blue-green atau rolling update lewat orkestrator seperti Docker Swarm jadi pilihan yang lebih tepat.
43.1.2 Rollback ke Versi Sebelumnya
Update yang gagal, misalnya container baru langsung crash atau HEALTHCHECK-nya terus unhealthy, harus bisa dibatalkan secepat mungkin. Kuncinya adalah selalu memakai tag image yang spesifik dan tidak pernah ditimpa ulang, bukan tag latest yang isinya bisa berubah kapan saja, supaya rollback tinggal menunjuk kembali ke tag versi lama yang masih tersimpan.
docker inspect --format='{{.Config.Image}}' web-app
docker compose stop web
docker run -d --name web-rollback -p 8080:80 registry.example.com/web-app:1.3.1Kalau memakai docker-compose.yml, cara yang lebih rapi adalah mengubah nilai tag pada image: kembali ke versi sebelumnya di file konfigurasi, lalu menjalankan ulang docker compose up -d supaya seluruh definisi service tetap konsisten dengan yang tertulis di file, bukan cuma container tunggal yang dijalankan manual lewat docker run. Di lapangan, tim yang disiplin menyimpan riwayat tag image di sistem version control (lewat file docker-compose.yml yang di-commit) supaya rollback bisa dilacak jelas siapa yang mengubah versi dan kapan, bukan sekadar mengandalkan ingatan.
43.1.3 Blue-Green Deployment Sederhana dengan Reverse Proxy
Blue-green deployment menjalankan dua set container sekaligus, versi lama (blue) dan versi baru (green), lalu mengalihkan traffic dari blue ke green hanya setelah green terbukti sehat. Pola ini menghindari downtime karena container lama tetap melayani traffic sampai container baru benar-benar siap. Pada satu host Docker tanpa orkestrator, reverse proxy seperti Nginx bisa berperan sebagai pengalih traffic tersebut.
docker run -d --name app-blue --network app-net registry.example.com/app:1.3.1
docker run -d --name app-green --network app-net registry.example.com/app:1.4.0
docker exec app-green wget -qO- http://localhost:3000/healthSetelah container app-green terbukti sehat lewat pemeriksaan endpoint health-nya, ubah konfigurasi upstream pada Nginx supaya mengarah ke app-green, lalu reload konfigurasinya tanpa menghentikan koneksi yang sedang berjalan.
docker exec nginx-proxy nginx -s reloadContainer app-blue baru dihentikan dan dihapus setelah traffic sepenuhnya berpindah dan tidak ada masalah yang muncul dalam beberapa saat pemantauan, sehingga rollback ke app-blue tetap memungkinkan tanpa perlu re-deploy dari awal kalau ternyata versi baru bermasalah.
docker rm -f app-blue43.2 Backup dan Recovery Data Container
Container itu sendiri bersifat disposable dan boleh dihapus kapan saja, tapi data yang tersimpan di volume atau di dalam database sering kali jadi aset paling berharga yang tidak boleh sampai hilang. Sysadmin/DevOps Engineer wajib punya prosedur backup yang teruji, bukan cuma berasumsi data aman karena "belum pernah ada masalah sebelumnya".
43.2.1 Backup Volume ke Arsip Tar
Cara paling portable untuk mencadangkan isi named volume adalah menjalankan container sementara yang me-mount volume tersebut sekaligus direktori host tempat menyimpan hasil backup, lalu mengarsipkannya dengan tar. Siapkan volume contoh dan isi sebuah file di dalamnya untuk bahan praktik.
docker volume create app-data
docker run --rm -v app-data:/data alpine sh -c "echo 'contoh data' > /data/catatan.txt"Jalankan backup dengan container Alpine sementara yang langsung dihapus setelah selesai (--rm), memakai image minimal supaya proses backup cepat dan tidak menambah beban resource yang tidak perlu.
docker run --rm -v app-data:/data -v "$(pwd)":/backup alpine \
tar czf /backup/app-data-backup.tar.gz -C /data .Perintah di atas mengarsipkan seluruh isi direktori /data (yang sebenarnya adalah volume app-data) ke file app-data-backup.tar.gz di direktori kerja host. Opsi -C /data membuat tar berpindah direktori dulu sebelum mengarsipkan, sehingga path di dalam arsip tetap relatif dan tidak menyeret struktur direktori /data yang absolut. Verifikasi isi arsip tanpa perlu mengekstraknya dulu.
tar tzf app-data-backup.tar.gz43.2.2 Restore Volume dari Backup
Proses restore memakai pola yang simetris dengan backup: buat volume tujuan, lalu ekstrak arsip ke dalamnya lewat container sementara yang sama.
docker volume create app-data-restored
docker run --rm -v app-data-restored:/data -v "$(pwd)":/backup alpine \
tar xzf /backup/app-data-backup.tar.gz -C /dataPastikan volume tujuan dalam kondisi kosong sebelum restore kalau tidak ingin file lama di dalamnya tertimpa campur dengan file hasil ekstraksi. Verifikasi hasil restore dengan memeriksa isi volume baru tersebut.
docker run --rm -v app-data-restored:/data alpine cat /data/catatan.txtDi lapangan, backup yang tidak pernah dicoba di-restore sama saja dengan tidak punya backup sama sekali, karena masalah seperti arsip yang korup atau permission yang salah baru ketahuan justru saat restore sungguhan dibutuhkan, biasanya di momen paling genting. Jadwalkan uji coba restore berkala, bukan cuma menjalankan backup lalu menganggapnya pasti berhasil.
43.2.3 Backup Database di Dalam Container
Untuk database seperti PostgreSQL atau MySQL/MariaDB yang berjalan di dalam container, backup level file volume seperti di atas berisiko menghasilkan data yang tidak konsisten kalau database sedang menulis transaksi saat file di-snapshot. Cara yang lebih aman adalah memakai tool dump resmi milik database tersebut lewat docker exec, yang bekerja di level logika data, bukan sekadar menyalin file mentah.
docker exec db-postgres pg_dump -U postgres appdb > appdb-backup.sqlPerintah ini menjalankan pg_dump di dalam container db-postgres, lalu mengalirkan hasilnya ke file appdb-backup.sql di host lewat redirection standar shell. Flag -t sengaja tidak disertakan karena mengalokasikan pseudo-TTY saat output sedang di-redirect ke file berisiko menyisipkan karakter carriage return tambahan yang merusak isi dump. Untuk MySQL/MariaDB, polanya serupa memakai mysqldump.
docker exec db-mysql mysqldump -u root -p appdb > appdb-backup.sqlRestore data dari file dump dilakukan sebaliknya, mengalirkan isi file lewat stdin ke dalam container database memakai docker exec -i (mode interactive tanpa alokasi TTY, supaya aliran data biner/teks tetap utuh).
cat appdb-backup.sql | docker exec -i db-postgres psql -U postgres -d appdbPerintah psql, mysqldump, dan sejenisnya berpotensi menimpa data yang sudah ada di database tujuan tanpa konfirmasi tambahan, jadi selalu pastikan dulu database tujuan restore memang benar-benar yang dimaksud sebelum menjalankan perintah ini, terutama kalau dijalankan langsung terhadap database production.
43.3 Resource Management di Production
Container yang tidak dibatasi resource-nya bisa menghabiskan seluruh CPU atau memory yang tersedia di host, membuat container lain di mesin yang sama ikut terganggu meski secara logis mereka tidak saling berhubungan. Menjaga batas resource yang jelas adalah salah satu tanggung jawab utama operasional container di production.
43.3.1 Membatasi CPU dan Memory
Batas resource ditentukan lewat flag saat container dijalankan, atau lewat kunci deploy.resources di docker-compose.yml untuk Compose versi terbaru.
docker run -d --name app-limited \
--cpus="1.5" --memory="512m" --memory-swap="512m" \
nginx:1.27-alpineOpsi --cpus membatasi jatah CPU dalam satuan jumlah core (nilai 1.5 berarti container maksimal memakai setara satu setengah core), sedangkan --memory membatasi RAM maksimal yang boleh dipakai. Opsi --memory-swap yang diset sama dengan --memory menonaktifkan swap tambahan, memastikan container benar-benar berhenti kalau melewati batas memory alih-alih diam-diam memakai swap yang jauh lebih lambat. Kalau container melewati batas --memory dan tidak ada swap tambahan, kernel Linux menghentikan proses di dalamnya lewat OOM killer (Out of Memory killer), yang tercatat di dmesg host maupun di exit code container yang terkait dengan OOM.
docker inspect --format='{{.State.OOMKilled}}' app-limitedPadanan konfigurasi ini di docker-compose.yml ditulis lewat kunci deploy.resources.limits.
services:
app:
image: nginx:1.27-alpine
deploy:
resources:
limits:
cpus: "1.5"
memory: 512MPerlu dicatat, kunci deploy pada dasarnya dirancang untuk Docker Swarm; saat dipakai lewat docker compose up biasa (bukan docker stack deploy), hanya sebagian sub-key termasuk resources.limits yang tetap diterapkan, sementara sub-key lain di bawah deploy seperti replicas diabaikan di luar mode Swarm.
43.3.2 Update Limit Resource Container Berjalan
Batas resource tidak harus ditentukan dari awal container dibuat; docker update mengubah limit CPU dan memory pada container yang sudah berjalan tanpa perlu menghentikan dan membuat ulang container tersebut.
docker update --cpus="2" --memory="1g" --memory-swap="1g" app-limitedFitur ini praktis saat Sysadmin/DevOps Engineer perlu menaikkan atau menurunkan jatah resource secara darurat, misalnya saat traffic melonjak mendadak, tanpa mengganggu proses yang sedang berjalan di dalam container. Perhatikan risiko menurunkan batas memory lewat docker update: kalau nilai baru berada di bawah pemakaian memory container saat itu juga, OOM killer bisa langsung bertindak begitu limit baru diterapkan, jadi periksa dulu pemakaian aktual lewat docker stats sebelum menurunkan batasnya.
docker stats app-limited --no-streamFlag --no-stream menampilkan satu snapshot data lalu langsung keluar, cocok dipakai di script otomasi dibanding mode default yang terus memperbarui tampilan secara live.
43.3.3 Restart Policy dan Automatic Recovery
Restart policy menentukan perilaku Docker daemon saat sebuah container berhenti sendiri, baik karena crash maupun karena daemon Docker sendiri baru saja restart. Pilihan kebijakan ini berdampak langsung pada seberapa cepat layanan pulih dari gangguan tanpa campur tangan manual.
| Restart Policy | Perilaku |
|---|---|
no | Tidak pernah restart otomatis (default kalau flag --restart tidak disertakan) |
on-failure[:N] | Restart hanya kalau proses berhenti dengan exit code bukan nol, maksimal N kali percobaan (opsional) |
always | Selalu restart apa pun exit code-nya, termasuk setelah daemon Docker atau host di-restart, kecuali container dihapus manual |
unless-stopped | Sama seperti always, tapi tidak ikut direstart setelah daemon Docker restart kalau container sebelumnya dihentikan manual lewat docker stop |
docker run -d --name app-resilient --restart unless-stopped nginx:1.27-alpineDi lapangan, unless-stopped jadi pilihan paling umum untuk service production yang dikelola manual di satu host Docker, karena tetap memberi kontrol penuh saat kita sengaja menghentikan container untuk maintenance, tanpa risiko container tersebut tiba-tiba hidup lagi sendiri setelah host di-reboot. Kebijakan on-failure lebih cocok untuk job atau task sekali jalan yang memang seharusnya berhenti normal setelah selesai, tapi perlu dicoba ulang kalau gagal karena error sementara. Ubah restart policy container yang sudah berjalan lewat docker update tanpa perlu membuat ulang container-nya.
docker update --restart on-failure:5 app-resilient43.4 Rutinitas Pemeliharaan Infrastruktur Docker
Mengetahui perintah update, backup, dan pembatasan resource saja belum cukup kalau semuanya masih dikerjakan manual satu per satu setiap kali dibutuhkan. Sub-bab ini merangkai praktik-praktik di atas jadi rutinitas operasional yang berjalan sendiri, sekaligus membahas dua celah yang sering luput dari perhatian Sysadmin/DevOps Engineer: riwayat event Docker yang ternyata tidak tersimpan permanen, dan rotasi log yang ternyata tidak otomatis berlaku ke container yang sudah lebih dulu berjalan.
43.4.1 Menyimpan Riwayat Docker Events secara Persisten
Perintah docker events menampilkan aliran event real-time dari Docker daemon setiap kali ada perubahan status pada container, image, volume, atau network. Yang sering luput diperhatikan, daemon Docker hanya menyimpan riwayat event ini di buffer memori berkapasitas 1000 entri terakhir dan tidak pernah menuliskannya ke disk secara permanen; begitu daemon restart atau buffer penuh, event lama yang belum sempat dicatat hilang tanpa bisa dipulihkan. Untuk kebutuhan audit trail yang benar-benar tahan lama, alirkan output docker events ke file log di host lewat proses yang berjalan terus-menerus di background.
nohup docker events --format json >> /var/log/docker-events.log 2>&1 &Perintah di atas menjalankan docker events di background lewat nohup, menulis setiap event dalam format JSON ke file /var/log/docker-events.log secara append, sehingga proses tetap hidup meski sesi terminal yang memulainya sudah ditutup. Untuk host production yang butuh proses ini tetap berjalan bahkan setelah reboot, bungkus perintah tersebut sebagai systemd service supaya otomatis start ulang lewat mekanisme restart-nya sendiri, terpisah dari restart policy Docker yang hanya berlaku untuk container.
[Unit]
Description=Docker Events Logger
After=docker.service
Requires=docker.service
[Service]
ExecStart=/usr/bin/docker events --format json
StandardOutput=append:/var/log/docker-events.log
Restart=always
[Install]
WantedBy=multi-user.targetSimpan unit file di atas sebagai /etc/systemd/system/docker-events-logger.service (konfigurasi ini berlaku untuk Linux dengan systemd; Docker Desktop di macOS/Windows tidak memakai mekanisme service serupa), lalu aktifkan.
sudo systemctl enable --now docker-events-logger.service
sudo systemctl status docker-events-logger.serviceSetelah service ini berjalan, file /var/log/docker-events.log jadi sumber histori event yang tidak lagi terikat batas 1000 entri di buffer memori daemon, dan bisa langsung ditelusuri lewat grep atau diteruskan ke stack observabilitas terpusat saat investigasi insiden yang terjadi jauh hari sebelumnya. Perlu dicatat, file log ini sendiri tetap perlu diawasi ukurannya lewat tool seperti logrotate di level OS, karena tidak otomatis dibatasi seperti halnya log container yang dikelola driver json-file.
43.4.2 Rutinitas Pemeliharaan Terjadwal Lewat Cron
Backup volume dan pembersihan resource menganggur yang sudah dibahas di sub-bab sebelumnya paling efektif kalau dijalankan otomatis secara berkala, bukan mengandalkan ingatan manual untuk menjalankannya setiap minggu. Gabungkan langkah backup dan pembersihan ke dalam satu script shell yang dijadwalkan lewat cron.
#!/bin/sh
set -e
BACKUP_DIR="/backup/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
docker run --rm -v app-data:/data -v "$BACKUP_DIR":/backup alpine \
tar czf /backup/app-data.tar.gz -C /data .
docker exec db-postgres pg_dump -U postgres appdb > "$BACKUP_DIR/appdb.sql"
docker system prune -f
find /backup -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;Simpan script di atas sebagai /usr/local/bin/docker-maintenance.sh, beri izin eksekusi, lalu jadwalkan lewat crontab supaya berjalan otomatis setiap hari di jam dengan traffic paling rendah.
chmod +x /usr/local/bin/docker-maintenance.sh
crontab -e0 3 * * * /usr/local/bin/docker-maintenance.sh >> /var/log/docker-maintenance.log 2>&1Baris find ... -mtime +30 pada script menghapus folder backup yang lebih tua dari 30 hari supaya direktori backup itu sendiri tidak ikut memenuhi disk tanpa batas; sesuaikan angka retensi ini dengan kebijakan backup organisasi masing-masing. Mengalihkan output cron ke file log terpisah seperti contoh di atas penting supaya kegagalan script, misalnya volume yang tidak ditemukan atau kredensial database yang berubah, tetap tercatat dan bisa diperiksa, alih-alih gagal diam-diam tanpa jejak apa pun.
Perintah docker system prune -f di dalam script bersifat destruktif terhadap container berhenti, network menganggur, dan image dangling (image tanpa tag yang tidak dipakai container manapun), meski secara default tidak menyentuh volume. Tinjau dulu dampaknya secara manual lewat docker system df -v, yang merangkum pemakaian disk per kategori resource Docker beserta kolom RECLAIMABLE yang menunjukkan ruang yang bisa dibebaskan, sebelum menambahkan opsi --volumes ke dalam script otomatis, supaya volume yang masih dibutuhkan tidak ikut terhapus tanpa sengaja di tengah malam saat tidak ada yang mengawasi.
43.4.3 Menerapkan Rotasi Log ke Container yang Sudah Berjalan
Konfigurasi rotasi log lewat opsi log-opts di daemon.json hanya berlaku sebagai nilai default untuk container yang dibuat setelah konfigurasi tersebut diterapkan. Container yang sudah berjalan sebelumnya tetap memakai konfigurasi log lama, karena log-driver dan log-opt ditentukan sekali saat container dibuat dan tidak termasuk opsi yang bisa diubah lewat docker update. Periksa dulu konfigurasi log yang sedang dipakai sebuah container untuk memastikan apakah rotasi sudah aktif.
docker inspect --format='{{json .HostConfig.LogConfig}}' app-lamaKalau hasilnya menunjukkan bagian Config kosong atau tidak ada key max-size, container tersebut masih memakai default tanpa batas ukuran, meski daemon.json di host sudah dikonfigurasi ulang setelah container ini dibuat. Satu-satunya cara menerapkan rotasi baru ke container yang sudah berjalan adalah membuat ulang container tersebut dengan opsi --log-opt eksplisit, mengikuti alur update yang sudah dibahas di awal bab ini, bukan sekadar merestart container yang sudah ada.
docker stop app-lama
docker run -d --name app-lama \
--log-opt max-size=10m --log-opt max-file=3 \
registry.example.com/app-lama:1.0Untuk file log json-file yang sudah telanjur membengkak sebelum rotasi diterapkan, Docker tidak menyediakan perintah bawaan untuk memangkas file yang sudah ada. Mengosongkan file tersebut langsung di level OS adalah cara paling umum dipakai untuk situasi darurat, memakai truncate, bukan rm.
sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' app-lama)truncate -s 0 mengosongkan isi file tanpa menghapus file itu sendiri, sehingga file descriptor yang sedang dipakai proses container tetap valid dan proses logging tidak terganggu. Menghapus file log secara langsung lewat rm justru berisiko membuat proses di dalam container kehilangan referensi ke file tersebut sambil terus menulis ke ruang disk yang sebenarnya sudah "dihapus" dari sistem file tapi belum benar-benar dibebaskan sampai proses container itu berhenti, kondisi yang sering bikin bingung karena df tetap melaporkan disk penuh padahal file log-nya sudah tidak terlihat lagi.

