Docker: od developmentu do produkcji

Kompletny przewodnik po Dockerze do konteneryzacji aplikacji. Dockerfile, Docker Compose, buildy multi-stage i wdrożenie produkcyjne z praktycznymi przykładami.

Przewodnik Docker od developmentu do produkcji

Docker rewolucjonizuje sposób tworzenia, testowania i wdrażania aplikacji. Poprzez enkapsulację aplikacji i jej zależności w przenośnym kontenerze, Docker eliminuje niesławny problem "u mnie działa" i zapewnia spójność we wszystkich środowiskach. Ten przewodnik obejmuje pełną drogę od pierwszego pliku Dockerfile do wdrożenia produkcyjnego.

Docker w 2026

Docker Desktop 5.x przynosi znaczące ulepszenia wydajności, w tym natywne wsparcie containerd, zoptymalizowane zarządzanie zasobami i bezproblemową integrację z Kubernetes. Obrazy wieloarchitekturowe (ARM/x86) są teraz standardową praktyką.

Podstawy konteneryzacji

Kontener to lekka jednostka oprogramowania, która pakuje kod, runtime, biblioteki systemowe i ustawienia. W przeciwieństwie do maszyn wirtualnych, które wirtualizują sprzęt, kontenery współdzielą jądro systemu hosta, co czyni je szybszymi w uruchamianiu i mniej zasobożernymi.

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

To polecenie pobiera obraz hello-world z Docker Hub i uruchamia kontener wyświetlający komunikat potwierdzający.

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 <container_id>

# Remove an image
docker rmi <image_name>

Te podstawowe polecenia zarządzają cyklem życia kontenerów i obrazów.

Tworzenie pierwszego Dockerfile

Dockerfile zawiera instrukcje do budowania obrazu Docker. Każda instrukcja tworzy warstwę w finalnym obrazie, umożliwiając cache'owanie i ponowne użycie.

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"]

Kolejność instrukcji jest kluczowa dla optymalizacji cache. Pliki, które rzadko się zmieniają (package.json), powinny być kopiowane przed kodem źródłowym.

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

Flaga -d uruchamia kontener w tle, -p mapuje port 3000 kontenera na port 3000 hosta.

Alpine vs Debian

Obrazy Alpine są znacznie mniejsze (około 5 MB w porównaniu z 120 MB dla Debiana). Używają jednak musl libc zamiast glibc, co może powodować niekompatybilności z niektórymi natywnymi zależnościami. W przypadku problemów zaleca się stosowanie obrazów opartych na Debianie (node:22-slim).

Buildy multi-stage dla produkcji

Buildy multi-stage tworzą zoptymalizowane obrazy produkcyjne, oddzielając środowisko budowania od środowiska uruchomieniowego. Tylko niezbędne artefakty są włączane do finalnego obrazu.

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"]

To podejście znacznie zmniejsza rozmiar finalnego obrazu, wykluczając narzędzia budowania, devDependencies i pliki źródłowe.

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

Redukcja rozmiaru może osiągnąć 60-70% w zależności od projektu, poprawiając czasy wdrożenia i zmniejszając powierzchnię ataku.

Docker Compose do lokalnej orkiestracji

Docker Compose upraszcza zarządzanie aplikacjami wielokontenerowymi. Plik YAML deklaruje wszystkie usługi, ich konfiguracje i zależności.

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

Usługi komunikują się za pomocą swoich nazw (db, cache) przez wewnętrzną sieć Docker. Healthchecki zapewniają gotowość zależności przed uruchomieniem aplikacji.

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

Gotowy na rozmowy o DevOps?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Zarządzanie sekretami i zmiennymi środowiskowymi

Bezpieczne zarządzanie sekretami jest kluczowe w produkcji. Docker oferuje kilka podejść w zależności od kontekstu.

yaml
# docker-compose.override.yml (development only)
version: "3.9"

services:
  app:
    env_file:
      - .env.development
    environment:
      - DEBUG=true

Dla produkcji, Docker secrets oferuje większe bezpieczeństwo.

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

Kod aplikacji odczytuje sekrety z zamontowanych plików.

config/secrets.jsjavascript
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'),
};

To podejście zapobiega ujawnianiu sekretów w zmiennych środowiskowych lub obrazach Docker.

Optymalizacja obrazów Docker

Kilka technik zmniejsza rozmiar obrazu i poprawia wydajność.

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"]

Użycie dumb-init zapewnia prawidłową obsługę sygnałów Unix, umożliwiając graceful shutdown kontenera.

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
Bezpieczeństwo obrazów

Obrazy należy regularnie skanować pod kątem podatności za pomocą narzędzi takich jak Trivy lub Snyk. Obrazy bazowe powinny być regularnie aktualizowane w celu uwzględnienia poprawek bezpieczeństwa.

Zaawansowane sieci Docker

Docker oferuje różne sterowniki sieciowe dla różnych przypadków użycia.

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

Ta konfiguracja izoluje usługi zgodnie z zasadą najmniejszych uprawnień. Baza danych jest dostępna wyłącznie przez 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

Wolumeny i trwałość danych

Wolumeny Docker zachowują dane poza cyklem życia kontenera.

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

Rozróżnienie między nazwanymi wolumenami a bind mountami jest istotne: wolumeny są zarządzane przez Dockera, podczas gdy bind mounty korzystają bezpośrednio z systemu plików hosta.

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

Wdrożenie produkcyjne

Solidny workflow wdrożeniowy obejmuje budowanie, testowanie i push do rejestru.

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"

Dla wdrożeń serwerowych, osobny plik compose produkcyjny dostosowuje konfigurację.

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"

Ta konfiguracja definiuje alokację zasobów, strategię aktualizacji i healthchecki dla niezawodnego wdrożenia.

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 i debugowanie kontenerów

Monitoring kontenerów jest niezbędny w produkcji.

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

Do zaawansowanego debugowania dostępnych jest kilka technik.

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:

Ten stos monitoringowy umożliwia zbieranie i wizualizację metryk kontenerów.

Podsumowanie

Docker transformuje cykl rozwoju, zapewniając spójność we wszystkich środowiskach. Konteneryzacja przynosi przenośność, izolację i powtarzalność — kluczowe cechy dla nowoczesnych aplikacji.

Checklista Docker dla produkcji

  • ✅ Buildy multi-stage dla zoptymalizowanych obrazów
  • ✅ Użytkownik nie-root w kontenerach
  • ✅ Healthchecki skonfigurowane dla wszystkich usług
  • ✅ Sekrety zarządzane przez Docker secrets lub bezpieczne zmienne środowiskowe
  • ✅ Limity zasobów (CPU, pamięć) zdefiniowane
  • ✅ Wolumeny dla trwałości krytycznych danych
  • ✅ Scentralizowane logowanie z rotacją plików
  • ✅ Skanowanie bezpieczeństwa obrazów przed wdrożeniem
  • ✅ Strategia aktualizacji bez przestojów
  • ✅ Izolowana sieć między usługami

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Opanowanie Dockera to fundamentalna umiejętność każdego współczesnego programisty. Od lokalnego środowiska po wdrożenie produkcyjne, Docker standaryzuje przepływy pracy i upraszcza operacje. Przedstawione tu koncepcje tworzą solidną podstawę do eksploracji Kubernetes i orkiestracji kontenerów na dużą skalę.

Tagi

#docker
#containerization
#devops
#docker compose
#deployment

Udostępnij

Powiązane artykuły