# Docker: dari Pengembangan ke Produksi > Panduan lengkap Docker untuk kontainerisasi aplikasi. Dockerfile, Docker Compose, multi-stage build, dan deployment produksi dengan contoh praktis. - Published: 2026-01-21 - Updated: 2026-04-10 - Author: SharpSkill - Tags: docker, containerization, devops, docker compose, deployment - Reading time: 18 min --- Docker merevolusi cara aplikasi dikembangkan, diuji, dan di-deploy. Dengan mengenkapsulasi aplikasi beserta dependensinya dalam kontainer portabel, Docker menghilangkan masalah terkenal "di mesin saya bisa jalan" dan menjamin konsistensi di semua lingkungan. Panduan ini membahas perjalanan lengkap dari Dockerfile pertama hingga deployment produksi. > **Docker di 2026** > > Docker Desktop 5.x membawa peningkatan performa signifikan, termasuk dukungan native containerd, manajemen resource yang dioptimalkan, dan integrasi Kubernetes yang mulus. Image multi-arsitektur (ARM/x86) kini menjadi praktik standar. ## Dasar-dasar Kontainerisasi Kontainer adalah unit perangkat lunak ringan yang mengemas kode, runtime, library sistem, dan pengaturan. Berbeda dengan mesin virtual yang memvirtualisasi hardware, kontainer berbagi kernel sistem host, menjadikannya lebih cepat untuk dijalankan dan lebih hemat resource. ```bash # terminal # Docker installation on Ubuntu sudo apt update sudo apt install -y docker.io # Add user to docker group (avoids sudo) sudo usermod -aG docker $USER # Verify installation docker --version # Docker version 26.1.0, build 1234567 # First container: downloads image and runs docker run hello-world ``` Perintah ini mengunduh image `hello-world` dari Docker Hub dan menjalankan kontainer yang menampilkan pesan konfirmasi. ```bash # terminal # List running containers docker ps # List all containers (including stopped) docker ps -a # List downloaded images docker images # Remove a container docker rm # Remove an image docker rmi ``` Perintah-perintah dasar ini mengelola siklus hidup kontainer dan image. ## Membuat Dockerfile Pertama Dockerfile berisi instruksi untuk membangun image Docker. Setiap instruksi membuat layer pada image akhir, memungkinkan caching dan penggunaan ulang. ```dockerfile # Dockerfile # Base image: Node.js 22 on Alpine Linux (lightweight) FROM node:22-alpine # Set working directory in the container WORKDIR /app # Copy dependency files first (cache optimization) COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Copy source code COPY . . # Expose port (documentation) EXPOSE 3000 # Startup command CMD ["node", "server.js"] ``` Urutan instruksi sangat penting untuk optimasi cache. File yang jarang berubah (package.json) harus disalin sebelum source code. ```bash # terminal # Build image with a tag docker build -t my-app:1.0 . # Run the container docker run -d -p 3000:3000 --name my-app-container my-app:1.0 # Check logs docker logs my-app-container # Access container shell docker exec -it my-app-container sh ``` Flag `-d` menjalankan kontainer di background, `-p` memetakan port 3000 kontainer ke port 3000 host. > **Alpine vs Debian** > > Image Alpine jauh lebih kecil (sekitar 5 MB dibandingkan 120 MB untuk Debian). Namun, image ini menggunakan musl libc alih-alih glibc, yang dapat menyebabkan inkompatibilitas dengan beberapa dependensi native. Jika terjadi masalah, disarankan menggunakan image berbasis Debian (node:22-slim). ## Multi-stage Build untuk Produksi Multi-stage build membuat image produksi yang dioptimalkan dengan memisahkan lingkungan build dari lingkungan runtime. Hanya artefak yang diperlukan yang disertakan dalam image akhir. ```dockerfile # Dockerfile.production # ============================================ # Stage 1: Build # ============================================ FROM node:22-alpine AS builder WORKDIR /app # Copy and install dependencies (including devDependencies) COPY package*.json ./ RUN npm ci # Copy source code COPY . . # Build the application (TypeScript, bundling, etc.) RUN npm run build # ============================================ # Stage 2: Production # ============================================ FROM node:22-alpine AS production # Non-root user for security RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Copy only necessary files from builder stage COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ # Switch to non-root user USER nodejs # Environment variables ENV NODE_ENV=production ENV PORT=3000 EXPOSE 3000 # Startup command CMD ["node", "dist/server.js"] ``` Pendekatan ini secara signifikan mengurangi ukuran image akhir dengan mengecualikan build tool, devDependencies, dan file sumber. ```bash # terminal # Build with specific file docker build -f Dockerfile.production -t my-app:production . # Compare image sizes docker images | grep my-app # my-app production abc123 150MB # my-app 1.0 def456 450MB ``` Pengurangan ukuran dapat mencapai 60-70% tergantung proyeknya, meningkatkan waktu deployment dan mengurangi attack surface. ## Docker Compose untuk Orkestrasi Lokal Docker Compose menyederhanakan pengelolaan aplikasi multi-kontainer. File YAML mendeklarasikan semua service, konfigurasinya, dan dependensinya. ```yaml # docker-compose.yml version: "3.9" services: # Main application app: build: context: . dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=development - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp - REDIS_URL=redis://cache:6379 volumes: # Mount source code for hot-reload - ./src:/app/src - ./package.json:/app/package.json depends_on: db: condition: service_healthy cache: condition: service_started networks: - app-network # PostgreSQL database db: image: postgres:16-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: secret POSTGRES_DB: myapp volumes: # Data persistence - postgres_data:/var/lib/postgresql/data # Initialization script - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 networks: - app-network # Redis cache cache: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data command: redis-server --appendonly yes networks: - app-network # Named volumes for persistence volumes: postgres_data: redis_data: # Dedicated network for isolation networks: app-network: driver: bridge ``` Service berkomunikasi menggunakan namanya (db, cache) melalui jaringan internal Docker. Healthcheck memastikan dependensi siap sebelum aplikasi dimulai. ```bash # terminal # Start all services docker compose up -d # View logs from all services docker compose logs -f # Logs from a specific service docker compose logs -f app # Stop and remove containers docker compose down # Removal including volumes (caution: data loss) docker compose down -v # Rebuild after Dockerfile changes docker compose up -d --build ``` ## Pengelolaan Secret dan Variabel Lingkungan Pengelolaan secret yang aman sangat penting di produksi. Docker menawarkan beberapa pendekatan tergantung konteksnya. ```yaml # docker-compose.override.yml (development only) version: "3.9" services: app: env_file: - .env.development environment: - DEBUG=true ``` Untuk produksi, Docker secrets memberikan keamanan yang lebih tinggi. ```yaml # docker-compose.production.yml version: "3.9" services: app: build: context: . dockerfile: Dockerfile.production secrets: - db_password - api_key environment: - NODE_ENV=production - DATABASE_PASSWORD_FILE=/run/secrets/db_password - API_KEY_FILE=/run/secrets/api_key secrets: db_password: file: ./secrets/db_password.txt api_key: file: ./secrets/api_key.txt ``` Kode aplikasi membaca secret dari file yang di-mount. ```javascript // config/secrets.js const fs = require('fs'); const path = require('path'); // Utility function to read Docker secrets function readSecret(secretName) { const secretPath = `/run/secrets/${secretName}`; // Check if secret file exists if (fs.existsSync(secretPath)) { return fs.readFileSync(secretPath, 'utf8').trim(); } // Fallback to classic environment variables const envVar = secretName.toUpperCase(); return process.env[envVar]; } module.exports = { databasePassword: readSecret('db_password'), apiKey: readSecret('api_key'), }; ``` Pendekatan ini mencegah secret terekspos di variabel lingkungan atau image Docker. ## Optimasi Image Docker Beberapa teknik mengurangi ukuran image dan meningkatkan performa. ```dockerfile # Dockerfile.optimized FROM node:22-alpine AS base # Install necessary tools in a single layer RUN apk add --no-cache \ dumb-init \ && rm -rf /var/cache/apk/* # ============================================ # Stage: Dependencies # ============================================ FROM base AS deps WORKDIR /app # Copy only lock files for caching COPY package.json package-lock.json ./ # Install with mounted npm cache (BuildKit) RUN --mount=type=cache,target=/root/.npm \ npm ci --only=production # ============================================ # Stage: Builder # ============================================ FROM base AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN --mount=type=cache,target=/root/.npm \ npm ci COPY . . RUN npm run build # ============================================ # Stage: Production # ============================================ FROM base AS production # Image metadata LABEL maintainer="team@example.com" LABEL version="1.0" LABEL description="Production-ready Node.js application" # Non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Copy production dependencies COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules # Copy build output COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/package.json ./ USER nodejs ENV NODE_ENV=production # dumb-init as PID 1 for signal handling ENTRYPOINT ["dumb-init", "--"] CMD ["node", "dist/server.js"] ``` Penggunaan `dumb-init` memastikan penanganan sinyal Unix yang benar, memungkinkan shutdown kontainer yang graceful. ```bash # terminal # Enable BuildKit for advanced features export DOCKER_BUILDKIT=1 # Build with cache and detailed output docker build --progress=plain -t my-app:optimized . # Analyze image layers docker history my-app:optimized # Detailed image inspection docker inspect my-app:optimized ``` > **Keamanan Image** > > Image harus dipindai secara berkala untuk menemukan kerentanan menggunakan tool seperti Trivy atau Snyk. Base image harus diperbarui secara berkala untuk menyertakan patch keamanan. ## Jaringan Docker Lanjutan Docker menawarkan berbagai driver jaringan untuk skenario penggunaan yang berbeda. ```yaml # docker-compose.networking.yml version: "3.9" services: # Publicly accessible frontend frontend: build: ./frontend ports: - "80:80" networks: - frontend-network - backend-network # API accessible only from frontend api: build: ./api networks: - backend-network - database-network # No external ports exposed # Isolated database database: image: postgres:16-alpine networks: - database-network # Accessible only by API networks: frontend-network: driver: bridge backend-network: driver: bridge internal: true # No internet access database-network: driver: bridge internal: true ``` Konfigurasi ini mengisolasi service mengikuti prinsip hak istimewa minimal. Database hanya dapat diakses oleh API. ```bash # terminal # Inspect Docker networks docker network ls # Details of a specific network docker network inspect app-network # Create a custom network docker network create --driver bridge --subnet 172.28.0.0/16 custom-network # Connect a container to an existing network docker network connect custom-network my-container ``` ## Volume dan Persistensi Data Volume Docker menyimpan data melampaui siklus hidup kontainer. ```yaml # docker-compose.volumes.yml version: "3.9" services: app: image: my-app:latest volumes: # Named volume for persistent data - app_data:/app/data # Bind mount for development - ./uploads:/app/uploads:rw # Read-only mount for configuration - ./config:/app/config:ro backup: image: alpine volumes: # Access same volume for backups - app_data:/data:ro - ./backups:/backups command: | sh -c "tar czf /backups/backup-$$(date +%Y%m%d).tar.gz /data" volumes: app_data: driver: local driver_opts: type: none device: /path/to/host/data o: bind ``` Perbedaan antara named volume dan bind mount penting: volume dikelola oleh Docker sementara bind mount langsung menggunakan filesystem host. ```bash # terminal # List volumes docker volume ls # Inspect a volume docker volume inspect app_data # Remove orphaned volumes docker volume prune # Backup a volume docker run --rm -v app_data:/data -v $(pwd):/backup alpine \ tar czf /backup/volume-backup.tar.gz /data ``` ## Deployment Produksi Alur deployment yang kokoh mencakup build, pengujian, dan push ke registry. ```bash # deploy.sh #!/bin/bash set -e # Variables REGISTRY="registry.example.com" IMAGE_NAME="my-app" VERSION=$(git describe --tags --always) echo "Building version: $VERSION" # Build production image docker build \ -f Dockerfile.production \ -t $REGISTRY/$IMAGE_NAME:$VERSION \ -t $REGISTRY/$IMAGE_NAME:latest \ --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ --build-arg VERSION=$VERSION \ . # Security scan echo "Running security scan..." docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy image $REGISTRY/$IMAGE_NAME:$VERSION # Push to registry echo "Pushing to registry..." docker push $REGISTRY/$IMAGE_NAME:$VERSION docker push $REGISTRY/$IMAGE_NAME:latest echo "Deployment complete: $REGISTRY/$IMAGE_NAME:$VERSION" ``` Untuk deployment server, file compose produksi terpisah menyesuaikan konfigurasi. ```yaml # docker-compose.prod.yml version: "3.9" services: app: image: registry.example.com/my-app:latest restart: always deploy: replicas: 3 resources: limits: cpus: "0.5" memory: 512M reservations: cpus: "0.25" memory: 256M update_config: parallelism: 1 delay: 10s failure_action: rollback healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s logging: driver: "json-file" options: max-size: "10m" max-file: "3" ``` Konfigurasi ini mendefinisikan alokasi resource, strategi update, dan healthcheck untuk deployment yang andal. ```bash # terminal # Production deployment with Docker Compose docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # Zero-downtime update (rolling update) docker compose -f docker-compose.yml -f docker-compose.prod.yml pull docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app # Rollback if issues occur docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps \ --scale app=0 && \ docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app ``` ## Monitoring dan Debugging Kontainer Monitoring kontainer sangat penting di produksi. ```bash # terminal # Real-time statistics for all containers docker stats # Statistics for a specific container with custom format docker stats my-app --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" # Inspect processes in a container docker top my-app # Real-time Docker events docker events --filter container=my-app # Copy files from/to a container docker cp my-app:/app/logs/error.log ./error.log ``` Untuk debugging mendalam, beberapa teknik tersedia. ```bash # terminal # Interactive shell in a running container docker exec -it my-app sh # Execute a single command docker exec my-app cat /app/config/settings.json # Start a container in debug mode docker run -it --rm --entrypoint sh my-app:latest # Inspect environment variables docker exec my-app printenv # Analyze logs with filters docker logs my-app --since 1h --tail 100 | grep ERROR ``` ```yaml # docker-compose.monitoring.yml version: "3.9" services: app: # ... existing configuration labels: - "prometheus.scrape=true" - "prometheus.port=3000" - "prometheus.path=/metrics" prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=15d' grafana: image: grafana/grafana:latest ports: - "3001:3000" volumes: - grafana_data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=secret volumes: prometheus_data: grafana_data: ``` Stack monitoring ini memungkinkan pengumpulan dan visualisasi metrik kontainer. ## Kesimpulan Docker mentransformasi siklus pengembangan dengan menjamin konsistensi di semua lingkungan. Kontainerisasi membawa portabilitas, isolasi, dan reprodusibilitas — kualitas esensial untuk aplikasi modern. ### Checklist Docker untuk Produksi - ✅ Multi-stage build untuk image yang dioptimalkan - ✅ User non-root di kontainer - ✅ Healthcheck dikonfigurasi untuk semua service - ✅ Secret dikelola via Docker secrets atau variabel lingkungan yang aman - ✅ Limit resource (CPU, memori) didefinisikan - ✅ Volume untuk persistensi data kritis - ✅ Logging terpusat dengan rotasi file - ✅ Pemindaian keamanan image sebelum deployment - ✅ Strategi update tanpa downtime - ✅ Jaringan terisolasi antar service Menguasai Docker adalah keterampilan fundamental bagi setiap developer modern. Dari lingkungan lokal hingga deployment produksi, Docker menstandarkan alur kerja dan menyederhanakan operasi. Konsep yang disajikan di sini membentuk fondasi yang kokoh untuk menjelajahi Kubernetes dan orkestrasi kontainer skala besar. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/devops/docker-from-development-to-production