适用场景:服务器资源有限、需要精细化控制每个容器启动参数、或希望深入理解 Docker 底层命令的生产环境。本指南完全基于 docker run 和原生 Docker 命令构建。


1. 核心架构与设计理念

在不使用 Docker Compose 的情况下,我们需要手动管理容器的生命周期、网络互联和数据卷挂载。

1.1 架构拓扑

[客户端] --> [Nginx 容器 (Port 80/443)] --> [App 容器 (Port 8000)]
                                    |
                                    |
                            [Backend Network]
                                    |
                    +-------------+-------------+
                    |             |             |
                [Postgres]     [Redis]      [MinIO]
                (Port 5432)   (Port 6379)  (Port 9000)

1.2 关键设计原则

  1. 网络隔离:创建独立的 Bridge 网络,后端服务不暴露端口到宿主机,仅通过容器名通信。
  2. 数据持久化:所有状态数据(数据库、日志、配置)必须挂载到宿主机目录。
  3. 资源限制:为每个容器设置 CPU 和内存上限,防止单个服务耗尽资源。
  4. 自动重启:配置 --restart unless-stopped 确保服务异常退出后自动恢复。

2. 基础设施准备

2.1 系统优化脚本

在生产环境部署前,执行以下优化以提升稳定性:

#!/bin/bash
# system-prep.sh
1. 增加文件描述符限制
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nofile 65536" >> /etc/security/limits.conf
2. 内核参数调优
cat < /etc/sysctl.d/99-docker-prod.conf
net.ipv4.ip_forward = 1
net.core.somaxconn = 65535
vm.max_map_count = 262144
fs.file-max = 65536
EOF
sysctl -p /etc/sysctl.d/99-docker-prod.conf
3. 创建项目目录结构
mkdir -p /opt/myapp/{logs,config,data,backups,ssl}
mkdir -p /opt/myapp/logs/{app,nginx,postgres,redis}
mkdir -p /opt/myapp/data/{postgres,redis,minio}

2.2 Docker Daemon 配置

编辑 /etc/docker/daemon.json 以优化全局日志和存储:

{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  },
  "live-restore": true
}

执行 systemctl restart docker 生效。


3. 网络与存储初始化

在启动任何容器之前,先创建基础架构资源。

3.1 创建专用网络

# 创建后端内部网络(外部无法直接访问)
docker network create --driver bridge backend-net
创建前端网络(可选,如果 Nginx 需要与其他外部服务通信)
docker network create --driver bridge frontend-net

3.2 准备数据卷目录

确保宿主机目录权限正确,避免容器内写入权限问题:

# 创建数据目录
mkdir -p /opt/myapp/data/postgres
mkdir -p /opt/myapp/data/redis
mkdir -p /opt/myapp/data/minio
设置权限(根据具体用户 ID 调整,通常 postgres 用户 ID 为 999)
chown -R 999:999 /opt/myapp/data/postgres

4. 服务部署详解

我们将按照依赖顺序启动服务:数据库/缓存 → 应用 → 反向代理。

4.1 部署 PostgreSQL 数据库

docker run -d \
  --name myapp-postgres \
  --network backend-net \
  --restart unless-stopped \
  -e POSTGRES_DB=myapp_db \
  -e POSTGRES_USER=myapp_user \
  -e POSTGRES_PASSWORD=SecurePassword123! \
  -v /opt/myapp/data/postgres:/var/lib/postgresql/data \
  -v /opt/myapp/logs/postgres:/var/log/postgresql \
  -p 127.0.0.1:5432:5432 \
  --memory=4g \
  --cpus=2.0 \
  postgres:15-alpine \
  -c max_connections=200

注意:-p 127.0.0.1:5432:5432 仅允许宿主机本地访问,增强安全性。

4.2 部署 Redis 缓存

docker run -d \
  --name myapp-redis \
  --network backend-net \
  --restart unless-stopped \
  -v /opt/myapp/data/redis:/data \
  -v /opt/myapp/logs/redis:/var/log/redis \
  -p 127.0.0.1:6379:6379 \
  --memory=1g \
  --cpus=1.0 \
  redis:7-alpine \
  redis-server --requirepass SecureRedisPass! --maxmemory 512mb --appendonly yes

4.3 部署应用服务 (App)

假设你已经构建了镜像 myapp:latest。

docker run -d \
  --name myapp-app \
  --network backend-net \
  --restart unless-stopped \
  -e APP_ENV=production \
  -e LOG_LEVEL=INFO \
  -e DATABASE_URL=postgresql://myapp_user:SecurePassword123!@myapp-postgres:5432/myapp_db \
  -e REDIS_URL=redis://:SecureRedisPass!@myapp-redis:6379/0 \
  -v /opt/myapp/logs/app:/app/logs \
  -v /opt/myapp/config:/app/config:ro \
  --memory=2g \
  --cpus=2.0 \
  --health-cmd="curl -f http://localhost:8000/health || exit 1" \
  --health-interval=30s \
  --health-timeout=10s \
  --health-retries=3 \
  myapp:latest

关键点:使用 --network backend-net 后,应用可以通过容器名 myapp-postgres 和 myapp-redis 直接解析 IP。

4.4 部署 Nginx 反向代理

docker run -d \
  --name myapp-nginx \
  --network frontend-net \
  --restart unless-stopped \
  -p 80:80 \
  -p 443:443 \
  -v /opt/myapp/config/nginx.conf:/etc/nginx/nginx.conf:ro \
  -v /opt/myapp/logs/nginx:/var/log/nginx \
  -v /opt/myapp/ssl:/etc/nginx/ssl:ro \
  --memory=512m \
  --cpus=0.5 \
  nginx:alpine

注意:Nginx 需要能访问 App 容器。如果 App 在 backend-net,Nginx 在 frontend-net,它们默认不通。
解决方案:将 Nginx 也加入 backend-net,或者创建一个共享网络。

# 将 Nginx 连接到后端网络,使其能解析 app 容器名
docker network connect backend-net myapp-nginx

5. 日志管理深度实践

在没有 Compose 的情况下,日志管理更依赖于宿主机文件和 Docker 驱动配置。

5.1 策略一:Docker JSON File 驱动(推荐)

这是最简单且性能较好的方式。我们在 daemon.json 中已经配置了全局限制,也可以针对特定容器覆盖:

# 示例:为高日志量应用单独配置
docker run -d \
  --name myapp-app \
  --log-driver json-file \
  --log-opt max-size=200m \
  --log-opt max-file=10 \
  ...其他参数...

查看日志:

# 实时查看
docker logs -f --tail 100 myapp-app
查看特定时间段
docker logs --since 2026-07-23T10:00:00 myapp-app

5.2 策略二:应用层文件日志 + 宿主机挂载

对于生产环境,建议应用在容器内将日志写入文件,并挂载到宿主机。这样即使容器崩溃,日志依然保留。

应用代码配置 (logger.py):

import logging
from logging.handlers import RotatingFileHandler
def setup_logger():
logger = logging.getLogger('app')
logger.setLevel(logging.INFO)
# 写入容器内的 /app/logs/app.log,该目录已挂载到宿主机
handler = RotatingFileHandler(
'/app/logs/app.log',
maxBytes=10010241024,
backupCount=10
)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
宿主机日志清理

由于是普通文件,可以使用 Linux 原生的 logrotate 进行管理。

创建 /etc/logrotate.d/myapp:

/opt/myapp/logs/app/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0644 root root
    postrotate
        # 如果应用支持信号重载日志句柄,在此处发送信号
        # docker kill -s HUP myapp-app
    endscript
}

5.3 策略三:集中式日志收集 (Syslog/Fluentd)

如果有多台服务器,建议将 Docker 日志驱动改为 syslog 或 fluentd,发送到中央日志服务器。

docker run -d \
  --name myapp-app \
  --log-driver syslog \
  --log-opt syslog-address=tcp://192.168.1.100:514 \
  --log-opt tag="myapp-production" \
  ...

6. 运维与维护脚本

由于没有 docker-compose.yml,我们需要编写 Shell 脚本来管理集群。

6.1 启动脚本 (start.sh)

#!/bin/bash
set -e
echo "🚀 启动生产环境服务..."
确保网络存在
docker network inspect backend-net >/dev/null 2>&1 || docker network create backend-net
启动基础服务
echo "Starting Database..."
docker start myapp-postgres || docker run -d --name myapp-postgres --network backend-net ... (参考上文完整命令)
echo "Starting Redis..."
docker start myapp-redis || docker run -d --name myapp-redis --network backend-net ...
等待数据库就绪
echo "Waiting for DB..."
sleep 5
启动应用
echo "Starting App..."
docker start myapp-app || docker run -d --name myapp-app --network backend-net ...
启动 Nginx
echo "Starting Nginx..."
docker start myapp-nginx || docker run -d --name myapp-nginx --network frontend-net ...
docker network connect backend-net myapp-nginx
echo "✅ 所有服务已启动"
docker ps --filter "name=myapp"

6.2 停止与清理脚本 (stop.sh)

#!/bin/bash
echo "🛑 停止服务..."
docker stop myapp-nginx myapp-app myapp-redis myapp-postgres
echo "🧹 清理未使用的资源..."
docker system prune -f

6.3 备份脚本 (backup.sh)

#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/opt/myapp/backups"
mkdir -p $BACKUP_DIR
echo "💾 备份 PostgreSQL..."
docker exec myapp-postgres pg_dump -U myapp_user myapp_db > $BACKUP_DIR/db_$DATE.sql
echo "💾 备份 Redis..."
docker exec myapp-redis redis-cli -a SecureRedisPass! SAVE
cp /opt/myapp/data/redis/dump.rdb $BACKUP_DIR/redis_$DATE.rdb
echo "✅ 备份完成: $BACKUP_DIR"
可选:删除 7 天前的备份
find $BACKUP_DIR -type f -mtime +7 -delete

7. 监控与健康检查

7.1 原生健康检查

在 docker run 中使用 --health-cmd 参数,Docker 引擎会自动监控容器状态。

docker inspect --format='{{.State.Health.Status}}' myapp-app

7.2 资源监控

使用 docker stats 实时监控:

docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" myapp-postgres myapp-app myapp-nginx

7.3 简单告警脚本

结合 Crontab 每 5 分钟检查一次:

#!/bin/bash
# check_health.sh
SERVICES=("myapp-postgres" "myapp-app" "myapp-nginx")
for svc in "${SERVICES[@]}"; do
if [ "$(docker inspect -f '{{.State.Running}}' $svc 2>/dev/null)" != "true" ]; then
echo "CRITICAL: Service $svc is down!" | mail -s "Alert: $svc Down" admin@example.com
# 尝试自动重启
docker start $svc
fi
done

8. 安全加固清单

  • 最小权限原则:
    • 不要以 root 运行应用容器(在 Dockerfile 中使用 USER appuser)。
    • 挂载配置文件时使用 :ro (Read-Only)。
  • 网络隔离:
    • 数据库和 Redis 绝不绑定 0.0.0.0,仅绑定 127.0.0.1 或通过内部 Docker 网络通信。
  • 秘密管理:
    • 避免在 docker run 命令历史中留下密码。
    • 建议使用 Docker Secrets 或环境变量文件 (--env-file .env)。
  • 镜像安全:
    • 定期扫描镜像漏洞:docker scan myapp:latest

更多推荐