本课将通过一个完整的全栈Web应用,综合运用前面学到的所有Docker知识。项目包括:
myapp/
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── nginx/
│ ├── Dockerfile
│ └── nginx.conf
├── docker-compose.yml
├── docker-compose.prod.yml
└── .env
# frontend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# backend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --chown=appuser:appuser . .
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
CMD ["node", "src/index.js"]
# nginx/nginx.conf
upstream backend {
server backend:3000;
}
server {
listen 80;
server_name localhost;
# 前端静态文件
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# API代理
location /api {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
}
}
# docker-compose.yml
version: '3.8'
services:
# PostgreSQL数据库
postgres:
image: postgres:15-alpine
container_name: myapp-postgres
environment:
POSTGRES_DB: ${DB_NAME:-myapp}
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis缓存
redis:
image: redis:7-alpine
container_name: myapp-redis
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
# 后端API
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: myapp-backend
environment:
NODE_ENV: ${NODE_ENV:-production}
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: ${DB_NAME:-myapp}
DB_USER: ${DB_USER:-postgres}
DB_PASSWORD: ${DB_PASSWORD:-secret}
REDIS_HOST: redis
REDIS_PORT: 6379
JWT_SECRET: ${JWT_SECRET}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- backend
- frontend
restart: unless-stopped
# 前端应用
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
args:
REACT_APP_API_URL: ${API_URL:-http://localhost/api}
container_name: myapp-frontend
depends_on:
- backend
networks:
- frontend
# Nginx反向代理
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
container_name: myapp-nginx
ports:
- "${PORT:-80}:80"
depends_on:
- frontend
- backend
networks:
- frontend
restart: unless-stopped
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
volumes:
postgres-data:
redis-data:
# docker-compose.prod.yml
version: '3.8'
services:
postgres:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
redis:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
backend:
deploy:
replicas: 3
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
nginx:
ports:
- "80:80"
- "443:443"
volumes:
- ./ssl:/etc/nginx/ssl:ro
deploy:
resources:
limits:
cpus: '1'
memory: 512M
# .env
NODE_ENV=production
DB_NAME=myapp
DB_USER=postgres
DB_PASSWORD=your_secure_password
JWT_SECRET=your_jwt_secret
API_URL=https://api.example.com
PORT=80
# .env.development
NODE_ENV=development
DB_NAME=myapp_dev
DB_USER=postgres
DB_PASSWORD=dev_password
JWT_SECRET=dev_secret
API_URL=http://localhost:3000/api
PORT=8080
-- init.sql
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
title VARCHAR(200) NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
// backend/healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: 3000,
path: '/health',
timeout: 2000
};
const request = http.request(options, (res) => {
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on('error', () => {
process.exit(1);
});
request.end();
#!/bin/bash
# deploy.sh
set -e
echo "Pulling latest code..."
git pull origin main
echo "Building images..."
docker-compose -f docker-compose.yml -f docker-compose.prod.yml build
echo "Running database migrations..."
docker-compose run --rm backend npm run migrate
echo "Starting services..."
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
echo "Waiting for services to be healthy..."
sleep 10
echo "Running health checks..."
curl -f http://localhost/health || exit 1
echo "Deployment successful!"
# 清理旧镜像
docker image prune -f
#!/bin/bash
# backup.sh
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
echo "Backing up database..."
docker exec myapp-postgres pg_dump -U postgres myapp > "$BACKUP_DIR/db_$DATE.sql"
echo "Backing up volumes..."
docker run --rm \
-v myapp_postgres-data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/postgres_$DATE.tar.gz /data
docker run --rm \
-v myapp_redis-data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/redis_$DATE.tar.gz /data
echo "Backup completed: $DATE"
# 删除7天前的备份
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
# docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
ports:
- "9090:9090"
networks:
- monitoring
grafana:
image: grafana/grafana
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
networks:
- monitoring
cadvisor:
image: google/cadvisor
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports:
- "8080:8080"
networks:
- monitoring
networks:
monitoring:
volumes:
prometheus-data:
grafana-data:
# 开发环境
docker-compose up -d
# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 查看日志
docker-compose logs -f
# 查看服务状态
docker-compose ps
# 停止服务
docker-compose down
# 停止并删除数据卷
docker-compose down -v
# 查看容器日志
docker-compose logs backend
docker-compose logs postgres
# 进入容器调试
docker-compose exec backend sh
docker-compose exec postgres psql -U postgres
# 检查网络连接
docker-compose exec backend ping postgres
docker-compose exec backend nc -zv postgres 5432
# 重启单个服务
docker-compose restart backend
# 查看资源使用
docker stats
恭喜你完成了Docker学习之路!通过这15节课,你已经掌握了: