Docker

0 60

Projekt-Setup

Wir erstellen eine vollständige Node.js/Express-API mit MongoDB, Redis und Nginx.

Projektstruktur

meine-api/
├── docker-compose.yml
├── Dockerfile
├── .dockerignore
├── .env.example
├── package.json
├── src/
│   ├── server.js
│   ├── routes/
│   └── models/
└── nginx/
    └── default.conf

Dockerfile für Node.js

# Multi-Stage Build für optimale Größe

# Build Stage
FROM node:18-alpine AS builder

WORKDIR /app

# Dependencies installieren
COPY package*.json ./
RUN npm ci --only=production

# Source Code kopieren
COPY . .

# Production Stage
FROM node:18-alpine

# Non-root User erstellen
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app

# Nur Production Dependencies und Code
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs ./src ./src
COPY --chown=nodejs:nodejs package*.json ./

# Als nodejs User ausführen
USER nodejs

EXPOSE 3000

ENV NODE_ENV=production

HEALTHCHECK --interval=30s --timeout=3s \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

CMD ["node", "src/server.js"]

.dockerignore

node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
*.md
.vscode
.idea
coverage
.nyc_output
dist
.dockerignore
Dockerfile
docker-compose.yml

package.json

{
  "name": "meine-api",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.5.0",
    "redis": "^4.6.7",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

server.js

const express = require('express');
const mongoose = require('mongoose');
const redis = require('redis');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(express.json());

// MongoDB Connection
mongoose.connect(process.env.MONGODB_URI || 'mongodb://mongo:27017/meine-api', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('MongoDB verbunden'))
.catch(err => console.error('MongoDB Fehler:', err));

// Redis Client
const redisClient = redis.createClient({
  url: process.env.REDIS_URL || 'redis://redis:6379'
});

redisClient.connect()
  .then(() => console.log('Redis verbunden'))
  .catch(err => console.error('Redis Fehler:', err));

// Health Check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// Beispiel-Route mit Caching
app.get('/api/data', async (req, res) => {
  try {
    // Prüfe Cache
    const cached = await redisClient.get('data');
    if (cached) {
      return res.json({ 
        source: 'cache',
        data: JSON.parse(cached) 
      });
    }

    // Daten von DB holen (Beispiel)
    const data = { message: 'Hallo von der API!', timestamp: new Date() };
    
    // In Cache speichern (10 Minuten)
    await redisClient.setEx('data', 600, JSON.stringify(data));
    
    res.json({ 
      source: 'database',
      data 
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server läuft auf Port ${PORT}`);
});

docker-compose.yml

version: '3.8'

services:
  # Node.js API
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      MONGODB_URI: mongodb://mongo:27017/meine-api
      REDIS_URL: redis://redis:6379
    volumes:
      - ./src:/app/src  # Nur für Development
    depends_on:
      mongo:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - app-network

  # MongoDB
  mongo:
    image: mongo:7.0
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: geheim123
      MONGO_INITDB_DATABASE: meine-api
    volumes:
      - mongo_data:/data/db
    healthcheck:
      test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
      interval: 10s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    networks:
      - app-network

  # Redis Cache
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped
    networks:
      - app-network

  # Nginx Reverse Proxy
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - api
    restart: unless-stopped
    networks:
      - app-network

volumes:
  mongo_data:
  redis_data:

networks:
  app-network:
    driver: bridge

Nginx Konfiguration

nginx/default.conf:

upstream api_backend {
    server api:3000;
}

server {
    listen 80;
    server_name localhost;

    # Logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # API Proxy
    location /api/ {
        proxy_pass http://api_backend/api/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    # Health Check
    location /health {
        proxy_pass http://api_backend/health;
    }

    # Static Files (falls vorhanden)
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ =404;
    }
}

Development vs. Production

docker-compose.override.yml (Development)

version: '3.8'

services:
  api:
    build:
      context: .
      target: builder
    command: npm run dev
    volumes:
      - ./src:/app/src
      - ./package.json:/app/package.json
      - /app/node_modules
    environment:
      NODE_ENV: development
    ports:
      - "9229:9229"  # Debug Port

docker-compose.prod.yml (Production)

version: '3.8'

services:
  api:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
      replicas: 3
      restart_policy:
        condition: on-failure
        max_attempts: 3
    environment:
      NODE_ENV: production

Starten

Development

# Alle Services starten
docker compose up -d

# Logs verfolgen
docker compose logs -f api

# In Container einsteigen
docker compose exec api sh

Production

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Testing

# Health Check
curl http://localhost/health

# API testen
curl http://localhost/api/data

# MongoDB Check
docker compose exec mongo mongosh -u admin -p geheim123

# Redis Check
docker compose exec redis redis-cli PING

Monitoring und Logging

Logs anzeigen

# Alle Logs
docker compose logs

# Nur API
docker compose logs -f api

# Letzte 100 Zeilen
docker compose logs --tail=100 api

# Logs mit Zeitstempel
docker compose logs -t api

Ressourcen überwachen

# Live Monitoring
docker compose stats

# Details zu einem Service
docker compose top api

Backup und Restore

MongoDB Backup

# Backup erstellen
docker compose exec mongo mongodump \
  --username admin \
  --password geheim123 \
  --out /data/backup

# Backup herunterladen
docker cp $(docker compose ps -q mongo):/data/backup ./mongo-backup

MongoDB Restore

# Backup hochladen
docker cp ./mongo-backup $(docker compose ps -q mongo):/data/

# Restore durchführen
docker compose exec mongo mongorestore \
  --username admin \
  --password geheim123 \
  /data/mongo-backup

Skalierung

# API auf 3 Instanzen skalieren
docker compose up -d --scale api=3

# Load Balancing konfigurieren (nginx anpassen)

CI/CD Integration

.gitlab-ci.yml

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  script:
    - docker compose -f docker-compose.test.yml up --abort-on-container-exit

deploy:
  stage: deploy
  script:
    - docker compose -f docker-compose.prod.yml pull
    - docker compose -f docker-compose.prod.yml up -d
  only:
    - main

Troubleshooting

Container startet nicht

# Logs prüfen
docker compose logs api

# Interaktiv debuggen
docker compose run --rm api sh

Verbindungsprobleme

# Netzwerk prüfen
docker compose exec api ping mongo
docker compose exec api ping redis

# DNS auflösen
docker compose exec api nslookup mongo

Performance-Probleme

# Ressourcen-Verbrauch
docker compose stats

# MongoDB Slow Queries
docker compose exec mongo mongosh --eval "db.currentOp()"

Best Practices

  1. Multi-Stage Builds: Kleinere Images
  2. Non-Root User: Sicherheit
  3. Health Checks: Automatische Neustarts
  4. Volumes für Daten: Persistenz
  5. .dockerignore: Schnellere Builds
  6. Environment Variables: Konfiguration
  7. Logging: Strukturiertes Logging
  8. Monitoring: Ressourcen überwachen

Zusammenfassung

Sie haben gelernt:

  • Node.js-App mit Docker containerisieren
  • Multi-Container-Setup mit Compose
  • Development vs. Production Config
  • Monitoring und Logging
  • Backup und Restore
  • Skalierung

Author: Andreas Lang

Sphinx-Flashdesign.de

Andreas Lang konzentriert sich seit zwei Jahrzehnten auf die Webentwicklung und Webdesign mit dem Schwerpunkt PHP, Laravel und Javascript und betreut seine Kunden mit Herz und Seele in allen Bereichen von Entwicklung, Design, Suchmaschinenoptimierung, IT-Recht, IT-Sicherheit etc.