结论先行:本地和生产共用一份 docker-compose.yml,一条 docker compose up -d 把 PostgreSQL、MySQL、Redis 全部拉起;数据落在命名卷(Named Volume)里,容器删了重建数据还在,换服务器只需复制配置文件加备份。记住三件事:用 docker compose 而非 docker run、数据落命名卷、再挂一条 cron 脚本定时备份。

搞独立开发最怕什么?数据库部署乱七八糟、数据莫名其妙丢了、换服务器重来一遍。

全景图解导览:Docker数据库部署实战:一条命令搞定… STAGE 01 · 问题起爆 写在前面 • 环节01:本地开发用 SQLite,上线要换成… • 环节02:每次换服务器都要… 定位核心风险卡点 STAGE 02 · 机制拆解 期望目标 • ✅一键部署:一条docker compose up-d搞定三个数据… • ✅数据持久化:容器删了数… 理清底层因果逻辑 STAGE 03 · 方案对照 计划思考 • 为什么用Docker?:传统安 装的问题: • 为什么用Docker Compose… 对齐量化实测标尺 STAGE 04 · 工程落地 操作步骤 • 消耗行数与流量最大的 SQL:(揪出未分页的暴力… • 执行时间最长的Top 10慢查… 锁死生产确定性

0、写在前面

你是不是也遇到过这些情况:

  • 本地开发用 SQLite,上线要换成 MySQL,改代码改到吐
  • 每次换服务器都要重新装数据库,配置文件都忘了上次怎么写的
  • 磁盘满了想迁移数据,结果发现数据库文件散落各处
  • 想做个备份,结果一堆命令记不住,翻文档翻半天

用 Docker 搞数据库,这些问题都能解决。

一串命令拉起来,数据自动持久化,备份恢复一条龙,换服务器只要复制配置文件。

这篇带你搞定三个最常用的数据库:PostgreSQL、MySQL、Redis。

问题现场解剖:写在前面 ⚠️ 从问题出发:表面症状是如何一步步演变为生产故障或降权的? 独立开发者工具箱 · CH-58 · 核心卡点传导链 1. 环节01 本地开发用SQLite,上线要换 成MySQL,改代码改到吐 症结:认知偏差与配置盲区 2. 环节02 每次换服务器都要重新装数据 库,配置文件都忘了上次怎么写… 传导:隐性劣化与边界失守 3. 磁盘满了想迁移数据 结果发现数据库文件散落各处 需通过真实环境实测核验 破局:量化指标与工程验证 ✅ 破局核心原则(结论先行) 本地和生产共用一份docker-compose.yml,一条docker compose up-d把 PostgreSQL、MySQL、Redis全部拉起;数据落在命名卷(Named Volume)里…

1、期望目标

这篇文章的目标是:

  • ✅ 一键部署:一条 docker compose up -d 搞定三个数据库
  • ✅ 数据持久化:容器删了数据还在,重启自动恢复
  • ✅ 健康检查:数据库挂了自动重启,服务无感知
  • ✅ 备份自动化:定时备份数据,一键恢复
  • ✅ 安全加固:默认端口改掉、限制访问、密码管理

做完这些,你就有了一个可复用的数据库环境,本地开发、测试、生产都能用。

底层机制与架构拆解:期望目标 四阶段递进分析模型 · 从前置识别到最终工程定级 S1 ✅一键部署 一条docker compose up -d搞定三个数据库 S2 ✅数据持久化 容器删了数据还在,重启 自动恢复 S3 ✅健康检查 数据库挂了自动重启,服 务无感知 S4 ✅备份自动化 定时备份数据,一键恢复 底层规则强约束 💡 工程启示:任何一个前置环节失守,都会通过传导链放大为下游的系统性偏差

2、计划思考

为什么用 Docker?

传统安装的问题:

对比维度 传统安装 Docker
环境污染 系统里装一堆依赖,多个版本冲突 容器隔离,互不影响
迁移成本 换服务器要重新装一遍 复制 docker-compose.yml 就行
数据管理 数据文件散落各处,找都找不到 统一用命名卷,位置固定
版本管理 升级可能破坏现有环境 换个镜像版本就是升级
回滚 很难回滚到旧版本 保留旧镜像,随时回滚

为什么用 Docker Compose 而不是单独 docker run?

单独 docker run 的问题:

  • 命令太长,记不住,每次都要翻文档
  • 多个容器之间的网络配置麻烦
  • 启动顺序要手动管

用 docker-compose.yml:

  • 配置写一次,反复用
  • 网络自动配置,服务名就是域名
  • 一条命令启动所有服务

数据持久化方案

Docker 数据库最怕的:容器删除 → 数据丢失

两种持久化方案对比:

方案 优点 缺点 推荐场景
Bind Mount(宿主机路径) 直观、可直接访问文件 权限问题多、跨平台麻烦 测试环境、需要直接访问文件
Named Volume(命名卷) Docker 管理、权限自动处理、跨平台 不能直接访问文件路径 生产环境、推荐方案

本文采用 Named Volume,省心。

Docker 数据库部署架构 VPS 服务器(一个 Docker Host) PostgreSQL 容器 端口 5432 数据 → postgres_data 配置 → postgres_conf MySQL 容器 端口 3306 数据 → mysql_data 配置 → mysql_conf Redis 容器 端口 6379 AOF 持久化 数据 → redis_data Docker Volume /var/lib/docker/volumes/... /backup 目录 cron 每日打包异地备份 容器可删,数据不可丢 , Volume + 备份是生命线

整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────┐
│ Docker Compose 环境 │
├─────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────┐ │
│ │PostgreSQL│ │ MySQL │ │ Redis │ │
│ │ :5432 │ │ :3307 │ │:6379 │ │
│ └────┬─────┘ └────┬─────┘ └───┬───┘ │
│ │ │ │ │
│ └────────────┴────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ backend_network │ │
│ └─────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Named Volumes (数据持久化) │ │
│ │ postgres_data │ mysql_data │ redis_data │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
▼
宿主机端口(仅开发环境暴露)

3、操作步骤

3.1 基础准备:安装 Docker

Ubuntu/Debian

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 更新软件包
sudo apt update

# 安装必要依赖
sudo apt install -y ca-certificates curl gnupg lsb-release

# 添加 Docker 官方 GPG 密钥
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 添加 Docker 仓库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 验证安装
docker --version
docker compose version

💡 懒人快速初始化

如果不想一步步配置 GPG 密钥、镜像源和用户组,推荐使用 ittinker-scripts 开源的生产级安装脚本(自带 50MB 日志轮转限制与 live-restore 保活):

1
curl -sSL https://raw.githubusercontent.com/ittinker/ittinker-scripts/main/docker-quick-deploy/deploy.sh | bash

如果在海外原生网络 VPS 上想用官方脚本:

1
curl -fsSL https://get.docker.com | sh

启动 Docker 并配置用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 启动 Docker 服务
sudo systemctl start docker
sudo systemctl enable docker

# 把当前用户加入 docker 组(不用每次都 sudo)
sudo usermod -aG docker $USER

# 重新登录或执行以下命令使权限生效
# Linux 系统
newgrp docker

# macOS 用户需要重新登录终端或执行:
# exec su - $USER

# 验证(不需要 sudo 了)
docker ps

3.2 创建项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
# 创建项目目录
mkdir -p ~/docker-databases
cd ~/docker-databases

# 创建子目录
mkdir -p postgres/init-scripts
mkdir -p mysql/init-scripts
mkdir -p redis/redis.conf
mkdir -p backups

# 目录结构
tree -L 2

目录结构应该是这样:

1
2
3
4
5
6
7
8
9
10
docker-databases/
├── docker-compose.yml # 主配置文件(文件名也叫 compose.yml,新版推荐)
├── .env # 环境变量(密码等敏感信息)
├── postgres/
│ └── init-scripts/ # PostgreSQL 初始化脚本
├── mysql/
│ └── init-scripts/ # MySQL 初始化脚本
├── redis/
│ └── redis.conf # Redis 配置文件
└── backups/ # 备份目录

3.3 创建环境变量文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 创建 .env 文件
cat > .env << 'EOF'
# ===== PostgreSQL 配置 =====
POSTGRES_VERSION=16-alpine
POSTGRES_DB=myapp
POSTGRES_USER=dbuser
POSTGRES_PASSWORD=change_this_postgres_password

# ===== MySQL 配置 =====
MYSQL_VERSION=8.0
MYSQL_DATABASE=myapp
MYSQL_USER=dbuser
MYSQL_PASSWORD=change_this_mysql_password
MYSQL_ROOT_PASSWORD=change_this_root_password

# ===== Redis 配置 =====
REDIS_VERSION=7-alpine
REDIS_PASSWORD=change_this_redis_password

# ===== 端口配置(避免冲突)=====
POSTGRES_PORT=5432
MYSQL_PORT=3307 # 改成 3307 避免和宿主机 MySQL 冲突
REDIS_PORT=6379
EOF

# 修改文件权限(只有你能读写)
chmod 600 .env

⚠️ 安全提醒

  • .env 包含明文密码,千万别提交到 Git!
  • 在 .gitignore 中添加 .env:
    1
    echo ".env" >> .gitignore
  • 生产环境建议用 Docker Secrets 或外部密钥管理工具。

3.4 创建主配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# 创建 docker-compose.yml
cat > docker-compose.yml << 'EOF'
# 注:Compose v2 已废弃顶层 version 字段,可直接写 services

services:
# ===== PostgreSQL =====
postgres:
image: postgres:${POSTGRES_VERSION:-16-alpine}
container_name: postgres_db
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./postgres/init-scripts:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- backend_network

# ===== MySQL =====
mysql:
image: mysql:${MYSQL_VERSION:-8.0}
container_name: mysql_db
restart: unless-stopped
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
ports:
- "${MYSQL_PORT:-3307}:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./mysql/init-scripts:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "MYSQL_PWD=${MYSQL_ROOT_PASSWORD} mysqladmin ping -h localhost -u root"]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
# 仅当客户端不支持默认的 caching_sha2_password 时才需要 mysql_native_password
# (新版 mysql2 / Prisma / PyMySQL 都已支持,多数情况可删掉下面这行)。
# 参数随版本不同,两者不能同时写,否则容器启动报 unknown variable 直接退出:
# MySQL 8.0 -> --default-authentication-plugin=mysql_native_password
# MySQL 8.4+ -> --mysql-native-password=ON(8.4 起 default-authentication-plugin 已移除)
command: --default-authentication-plugin=mysql_native_password
networks:
- backend_network

# ===== Redis =====
redis:
image: redis:${REDIS_VERSION:-7-alpine}
container_name: redis_cache
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a $REDIS_PASSWORD ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend_network

# ===== Adminer(数据库管理工具,可选)=====
adminer:
image: adminer:latest
container_name: adminer
restart: unless-stopped
ports:
- "8080:8080"
networks:
- backend_network
profiles:
- tools # 默认不启动,需要时用 --profile tools

volumes:
postgres_data:
driver: local
mysql_data:
driver: local
redis_data:
driver: local

networks:
backend_network:
driver: bridge
EOF

3.5 创建初始化脚本(可选)

如果需要在数据库启动时自动创建表或用户,可以放初始化脚本。

PostgreSQL 初始化脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat > postgres/init-scripts/01-init.sql << 'EOF'
-- 创建扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- 创建示例表
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入测试数据
INSERT INTO users (username, email)
VALUES
('test_user', '[email protected]')
ON CONFLICT (username) DO NOTHING;
EOF

MySQL 初始化脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat > mysql/init-scripts/01-init.sql << 'EOF'
-- 创建示例表
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入测试数据
INSERT INTO users (username, email)
VALUES
('test_user', '[email protected]')
ON DUPLICATE KEY UPDATE username=username;
EOF

💡 初始化脚本执行时机

  • 只在首次创建命名卷时执行一次
  • 后续重启容器不会再执行
  • 如果需要重新执行,要删除卷:docker volume rm docker-databases_postgres_data 然后重新启动

3.6 启动所有数据库

1
2
3
4
5
6
7
8
9
10
11
# 启动所有服务(后台运行)
docker compose up -d

# 查看启动日志
docker compose logs -f

# 等待健康检查通过(约 30-60 秒)
# 看到 "healthy" 说明启动完成

# 查看容器状态
docker compose ps

正常启动后,docker compose ps 输出类似:

1
2
3
4
NAME           IMAGE          STATUS          PORTS
postgres_db postgres:16 Up 30 seconds 0.0.0.0:5432->5432/tcp
mysql_db mysql:8.0 Up 35 seconds 0.0.0.0:3307->3306/tcp
redis_cache redis:7-alpine Up 28 seconds 0.0.0.0:6379->6379/tcp

3.7 连接测试

PostgreSQL

1
2
3
4
5
6
# 连接 PostgreSQL(在宿主机执行)
docker exec -it postgres_db psql -U dbuser -d myapp

# 在 psql 中执行测试
SELECT * FROM users;
\q # 退出

MySQL

1
2
3
4
5
6
7
# 连接 MySQL(注意 -p 和数据库名之间有空格,会提示输入密码)
docker exec -it mysql_db mysql -u dbuser -p myapp

# 在 MySQL 中执行测试
USE myapp;
SELECT * FROM users;
exit; # 退出

Redis

1
2
3
4
5
6
7
8
# 连接 Redis
docker exec -it redis_cache redis-cli -a change_this_redis_password

# 在 Redis 中执行测试
PING # 应该返回 PONG
SET test "Hello Docker"
GET test
exit # 退出

从外部应用连接

⚠️ 使用实际密码
下面的命令把密码替换成你在 .env 里设置的实际密码!

1
2
3
4
5
6
7
8
# PostgreSQL
psql -h localhost -U dbuser -d myapp -p 5432

# MySQL(注意端口是 3307)
mysql -h localhost -u dbuser -p myapp -P 3307

# Redis
redis-cli -h localhost -p 6379 -a change_this_redis_password

💡 VPS 上部署安全提醒

生产环境不要暴露数据库端口到公网!如果必须远程连接:

  • 用 VPN 或 SSH 隧道
  • 配置防火墙只允许特定 IP
  • 使用强密码

3.8 数据备份与恢复

PostgreSQL 备份/恢复

1
2
3
4
5
6
7
8
# 备份
docker exec postgres_db pg_dump -U dbuser myapp > backups/postgres_backup_$(date +%Y%m%d).sql

# 恢复
cat backups/postgres_backup_20260702.sql | docker exec -i postgres_db psql -U dbuser -d myapp

# 或使用 pg_restore(如果备份是自定义格式)
docker exec postgres_db pg_restore -U dbuser -d myapp < backups/postgres_backup.dump

MySQL 备份/恢复

1
2
3
4
5
# 备份(会提示输入密码)
docker exec mysql_db mysqldump -u dbuser -p myapp > backups/mysql_backup_$(date +%Y%m%d).sql

# 恢复(会提示输入密码)
cat backups/mysql_backup_20260702.sql | docker exec -i mysql_db mysql -u dbuser -p myapp

Redis 备份/恢复

1
2
3
4
5
6
7
8
# Redis 数据会自动持久化到 /data/appendonly.aof
# 备份:直接复制 AOF 文件
docker cp redis_cache:/data/appendonly.aof backups/redis_backup_$(date +%Y%m%d).aof

# 恢复:停止容器,复制回文件,重启
docker compose stop redis
docker cp backups/redis_backup_20260702.aof redis_cache:/data/appendonly.aof
docker compose start redis

定时自动备份

创建备份脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cat > backup.sh << 'EOF'
#!/bin/bash
# 加载环境变量
source .env || export $(grep -v '^#' .env | xargs)

BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

# PostgreSQL
docker exec postgres_db pg_dump -U ${POSTGRES_USER:-dbuser} ${POSTGRES_DB:-myapp} > $BACKUP_DIR/postgres_${DATE}.sql

# MySQL
docker exec mysql_db mysqldump -u ${MYSQL_USER:-dbuser} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE:-myapp} > $BACKUP_DIR/mysql_${DATE}.sql

# Redis
docker cp redis_cache:/data/appendonly.aof $BACKUP_DIR/redis_${DATE}.aof

# 删除 7 天前的备份
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.aof" -mtime +7 -delete

echo "Backup completed: ${DATE}"
EOF

chmod +x backup.sh

添加到 crontab(每天凌晨 2 点自动备份):

1
2
3
4
5
# 编辑 crontab
crontab -e

# 添加这一行(自动适应当前用户路径)
0 2 * * * cd $HOME/docker-databases && ./backup.sh >> /var/log/db_backup.log 2>&1

3.9 数据库健康与用量巡检(防慢查询与资源透支)

无论是自建 VPS 上的 Docker PostgreSQL,还是 Supabase 托管库,随着业务数据量攀升,缺乏索引的全表扫描(Seq Scan)和未加 LIMIT 的宽表查询都会迅速跑满 CPU,甚至带来天价带宽用量。

建议使用 ittinker-scripts 开源的 supabase-usage-guard 脚本进行健康诊断:

1
2
# 在宿主机直接对容器内的 PostgreSQL 执行 360 度用量与性能体检
docker exec -i postgres_db psql -U ${POSTGRES_USER:-dbuser} -d ${POSTGRES_DB:-myapp} < <(curl -sSL https://raw.githubusercontent.com/ittinker/ittinker-scripts/main/supabase-usage-guard/diagnose-db-health.sql)

该工具会精准定位出:

  • 消耗行数与流量最大的 SQL(揪出未分页的暴力查询)
  • 执行时间最长的 Top 10 慢查询
  • 缺少索引导致大量全表顺序扫描的表
  • 长期无访问却占用磁盘的冗余索引与死元组膨胀率

3.10 生产环境安全加固

修改默认端口

生产环境建议修改 .env 中的端口,不使用默认端口:

1
2
3
4
# 修改 .env
POSTGRES_PORT=15432
MYSQL_PORT=13307
REDIS_PORT=16379

限制外部访问

如果数据库只给内部应用使用,不要暴露端口:

1
2
3
# 在 docker-compose.yml 中删除 ports 部分,或只绑定到 localhost
ports:
- "127.0.0.1:5432:5432" # 只允许本机访问

配置防火墙

1
2
3
4
5
6
7
8
9
10
11
12
13
# 先查看你的公网 IP(从服务器执行)
curl ifconfig.me

# 允许你的 IP 访问(替换成上面查到的 IP)
sudo ufw allow from YOUR_IP_HERE to any port 5432
sudo ufw allow from YOUR_IP_HERE to any port 3307

# 如果你是通过 SSH 连接的,YOUR_IP_HERE 就是你本地电脑的公网 IP

# 或允许所有出站,拒绝所有入站(除了 SSH 和必要端口)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH

资源限制

防止数据库占用过多资源:

1
2
3
4
5
6
7
8
9
# 在 docker-compose.yml 的服务中添加
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
落地执行与验证流水线:操作步骤 按序执行以下四步流程,把定性经验转化为可自动巡检的工程防线 STEP 01 消耗行数与流量最大的… (揪出未分页的暴力查 询) STEP 02 执行时间最长的Top 10… 缺少索引导致大量全表顺 序扫描的表 STEP 03 长期无访问却占用磁盘… 10生产环境安全加固 可复现验收标准 STEP 04 基础准备:安装… 基础准备:安装Docker 可复现验收标准

4、总结

关键配置清单

检查项 命令 预期结果
Docker 已安装 docker --version 显示版本号
Docker Compose 已安装 docker compose version 显示版本号
容器运行中 docker compose ps 3 个容器都是 “Up”
健康检查通过 docker compose ps 状态显示 “healthy”
数据持久化 docker volume ls 显示 3 个命名卷
可连接数据库 测试连接命令 能正常连接并查询
备份脚本可执行 chmod +x backup.sh 有执行权限

日常维护命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 启动所有服务
docker compose up -d

# 停止所有服务
docker compose down

# 停止但保留数据(默认行为)
docker compose down

# 停止并删除数据卷(⚠️ 会丢数据!)
docker compose down -v

# 查看日志
docker compose logs -f

# 查看特定服务日志
docker compose logs -f postgres

# 重启某个服务
docker compose restart postgres

# 更新镜像版本
docker compose pull
docker compose up -d

# 进入容器 Shell
docker exec -it postgres_db sh

最佳实践

  1. 永远用 .env 管理敏感信息,不要写死在 docker-compose.yml
  2. 定期测试备份恢复,备份文件要定期验证
  3. 监控磁盘空间,数据库日志和数据会不断增长
  4. 生产环境用固定版本标签(如 postgres:16-alpine),不要用 latest
  5. 限制容器资源,防止一个数据库把服务器资源吃光
  6. 定期更新镜像,获取安全补丁:docker compose pull && docker compose up -d

常用 VPS 推荐

部署数据库需要稳定的 VPS,我用过几家,整理了推荐:

多维实测与判断标尺矩阵:总结 检查项 命令 预期结果 Docker已安装 docker--version 显示版本号 Docker Compose已安… docker compose version 显示版本号 容器运行中 docker compose ps 3个容器都是"Up" 健康检查通过 docker compose ps 状态显示"healthy"

5、异常记录

Q1:容器启动失败,提示端口被占用?

现象:docker compose up -d 报错 Bind for 0.0.0.0:3306 failed: port is already allocated

原因:宿主机已经有一个服务在占用这个端口

解决方法 1:修改 .env 中的端口号

1
2
# 修改 .env
MYSQL_PORT=3307 # 换个端口

解决方法 2:找到并停止占用的进程

1
2
3
4
5
6
# 查看端口占用
sudo lsof -i :3306
sudo netstat -tulpn | grep :3306

# 停止占用的服务
sudo systemctl stop mysql # 如果是宿主机 MySQL

Q2:数据库连接不上?

排查步骤:

1
2
3
4
5
6
7
8
9
10
11
# 1. 确认容器在运行
docker compose ps

# 2. 查看健康检查状态
docker inspect postgres_db | grep -A 10 Health

# 3. 查看日志
docker compose logs postgres

# 4. 确认端口映射
docker port postgres_db

常见原因:

  • 健康检查还没通过(等 30-60 秒)
  • 密码错误(检查 .env)
  • 端口映射错误(检查 docker-compose.yml 的 ports 部分)

Q3:重启容器后数据丢失?

确认是否用了命名卷:

1
2
3
4
5
# 查看卷列表
docker volume ls

# 查看卷详情
docker inspect docker-databases_postgres_data

如果卷存在但数据没了,可能是:

  • 删卷时误用了 docker compose down -v(带 -v 会连带删除数据卷!)
  • 挂载路径写错了

预防:

  • 维护停机不要加 -v,直接执行 docker compose down 即可
  • 备份!备份!备份!

Q4:容器占用的磁盘空间越来越大?

查看空间占用:

1
2
3
4
5
# 查看 Docker 占用的磁盘空间
docker system df

# 查看详细占用
du -sh /var/lib/docker/*

清理方法:

1
2
3
4
5
6
7
8
9
10
# 清理未使用的镜像、容器、网络、构建缓存
docker system prune -a

# 只清理未镜像的镜像(保留当前使用的)
docker image prune

# 清理日志(如果日志太大了)
docker compose down
# 然后重新启动
docker compose up -d

预防:

  • 配置日志轮转
1
2
3
4
5
6
7
services:
postgres:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

Q5:权限错误(PostgreSQL 常见)?

现象:容器启动失败,日志显示 initdb: error: directory "/var/lib/postgresql/data" exists but is not empty 或 Permission denied

解决方法:

1
2
3
4
5
# 给卷目录正确权限(一般不需要,Docker 会自动处理)
# 如果遇到问题,可以尝试重建卷
docker compose down
docker volume rm docker-databases_postgres_data
docker compose up -d

Q6:性能很慢?

排查:

1
2
3
4
5
# 查看容器资源占用
docker stats

# 进入容器查看进程
docker exec -it postgres_db top

优化:

  • 给容器分配更多资源(配置 deploy.resources.limits)
  • 检查宿主机是否有足够内存
  • 数据库配置优化(修改配置文件)

Q7:忘记数据库密码?

查看 .env 文件:

1
cat .env | grep PASSWORD

如果 .env 丢了,只能重置:

1
2
3
4
5
6
7
8
9
# 停止容器
docker compose down

# 删除卷(⚠️ 数据会丢!)
docker volume rm docker-databases_postgres_data

# 修改 .env 中的密码
# 重新启动
docker compose up -d

这就是备份的重要性!

问题现场解剖:异常记录 ⚠️ 从问题出发:表面症状是如何一步步演变为生产故障或降权的? 独立开发者工具箱 · CH-58 · 核心卡点传导链 1. Q1:容器启动失败,提示… 现象:docker compose up-d 报错Bind for 0.0.0.0:3306… 症结:认知偏差与配置盲区 2. Q2:数据库连接不上? 排查步骤: 需通过真实环境实测核验 传导:隐性劣化与边界失守 3. Q3:重启容器后数据丢… 确认是否用了命名卷: 需通过真实环境实测核验 破局:量化指标与工程验证 ✅ 破局核心原则(结论先行) 本地和生产共用一份docker-compose.yml,一条docker compose up-d把 PostgreSQL、MySQL、Redis全部拉起;数据落在命名卷(Named Volume)里…

6、下一步

数据库部署好了,下一步可能是:

  • 部署应用:Next.js / Django / Laravel 如何连接这些数据库
  • 监控告警:Grafana + Prometheus 监控数据库性能
  • 高可用方案:主从复制、集群部署
  • 自动化运维:CI/CD 集成、自动备份、自动升级

需要的话,我都可以写。

问题整改前后解剖:下一步 ⚠️ 整改前:常见踩坑与失分现场 • 盲区 1:忽视「部署应用」边界约束 • 盲区 2:缺乏「监控告警」实测核验 • 盲区 3:遗漏「高可用方案」兜底监控 后果:线上隐性故障频发 / 评估等级下调 ✅ 整改后:高确定性工程实践 • 动作 1:部署应用:Next.js/Django/Laravel如何连… • 动作 2:监控告警:Grafana+Prometheus监控数据库… • 动作 3:高可用方案:主从复制、集群部署 收益:指标可观测、配置可复现、风险可阻断

7、FAQ(独立开发者最常问的几个问题)

Q1:PostgreSQL 和 MySQL,独立开发者选哪个?

没有唯一答案,看你的技术栈。PostgreSQL 更严格,JSONB、全文检索、地理空间(PostGIS)更强,适合复杂查询和将来扩展;MySQL 生态最熟、共享主机支持最广、运维资料最多。新项目且没有历史包袱,很多人倾向 PostgreSQL;如果你的部署环境对 MySQL 支持更好(某些廉价主机、老教程),选 MySQL 也完全够用。两个都生产可用,别在这上面纠结太久。

Q2:SQLite 用得好好的,什么时候该换 Docker 数据库?

单机、低并发、读写不密集,SQLite 完全够用,很多站点甚至跑 Litestream + SQLite 也很稳。该换的信号:需要多个进程或容器并发写(SQLite 的写锁是库级别的)、数据库和应用要部署在不同机器、需要标准的网络备份或主从方案、或想让生产环境和开发用同一种数据库。这几个都不沾边,就别折腾。

Q3:Apple Silicon(M1/M2/M3)能跑这套吗?

postgres 和 redis 的官方镜像都原生支持 arm64,直接跑。MySQL 要留意:官方 mysql:8.0(Debian 版)在 M 系列上常报 no matching manifest for linux/arm64/v8,常见三种解法:换 mariadb(兼容 MySQL 协议、原生 arm64)、用 mysql:8.0-oracle(Oracle 提供了 arm64 构建)、或在 compose 里给 mysql 服务加 platform: linux/x86_64 走 x86 模拟(能用但性能会降)。

Q4:数据落了命名卷,还要做备份吗?

一定要。命名卷只保证"删容器、重启"不丢数据,防不住磁盘损坏、误执行 docker compose down -v、被勒索或误删,以及一次写错的迁移把表搞烂。备份和持久化是两件事,按文中的 cron 脚本定时跑,并且定期演练恢复,没验证过的备份等于没有。

Q5:一台小 VPS(2 核 2G)带得动三个数据库吗?

能跑,但别三个一起压。空载时 PostgreSQL 约 30-50MB、Redis 很小、MySQL 8.0 默认就吃 400MB 上下,三个加操作系统在 2G 上偏紧。开发或低流量够用;生产建议 4G 起步,而且只开你真正在用的那个数据库,缓存和主库按需分开加,别一上来三个全堆上去。


底层机制与架构拆解:FAQ(独立开发者最常问的几个问题) 四阶段递进分析模型 · 从前置识别到最终工程定级 S1 输入识别与场景分流 明确核心业务诉求与上下 文边界条件 S2 底层协议与规则校验 对照核心规范与真实负载 指标逐项核验 S3 瓶颈隔离与风险阻断 剔除干扰链路、单点隐患 与高危配置 S4 确定性架构标准输出 沉淀为可一键复现的工程 配置基线 💡 工程启示:任何一个前置环节失守,都会通过传导链放大为下游的系统性偏差

❓ FAQ(Docker 容器最常问)

Q:docker-compose v1 和 v2 区别是什么?要换吗?
v1 已 EOL(2023-06)。v2 用 docker compose(空格)整合进 Docker CLI,安装即用、无需 Python。v1 的 docker-compose.yml 文件 v2 完全兼容,直接 docker compose up -d 就行。

Q:容器一重启数据就丢,怎么持久化?
匿名卷:-v /var/lib/postgresql/data(Docker 自动分配,难备份)。命名卷:-v postgres_data:/var/lib/postgresql/data(推荐,跨容器复用)。绑定挂载:-v ./data:/var/lib/postgresql/data(最灵活,适合开发)。生产用命名卷。

Q:depends_on 能保证服务启动顺序,但能保证「就绪」吗?
不能,depends_on 只等容器启动,不等服务可用。数据库要就绪必须配 healthcheck + condition: service_healthy。这是新手最常踩的坑。

Q:bridge / host / none 网络模式怎么选?
bridge(默认):容器间通过虚拟网络互通,推荐大多数场景。host:容器直接用主机网络(性能好但端口冲突)。none:完全隔离,适合安全敏感场景。自定义网络:bridge 升级版,支持 DNS + 多子网。

Q:Portainer / Dockge / LazyDocker 三款怎么选?
Portainer:功能最全(多集群/权限/审计),重,适合团队。Dockge:轻量、单 docker-compose、UI 简洁,适合个人。LazyDocker:纯终端 TUI,适合 SSH 党。个人 → Dockge;团队 → Portainer;终端党 → LazyDocker。

问题整改前后解剖:❓FAQ(Docker容器最常问) ⚠️ 整改前:常见踩坑与失分现场 • 盲区 1:忽视「首屏与入口响应」边界约束 • 盲区 2:缺乏「边界容错与防线」实测核验 • 盲区 3:遗漏「可验证证据链」兜底监控 后果:线上隐性故障频发 / 评估等级下调 ✅ 整改后:高确定性工程实践 • 动作 1:首屏与入口响应:剥离冗余阻塞,核心功能与结… • 动作 2:边界容错与防线:显式设置超时熔断与异常输入… • 动作 3:可验证证据链:配备实机压测指标、日志与可复… 收益:指标可观测、配置可复现、风险可阻断

数据与引用

  • PostgreSQL 开发者采用率 55.6%(2025 Stack Overflow 全球调研,49000+ 开发者):连续三年成为最受开发者喜爱的数据库,反超 MySQL 成为第一。
    来源:https://survey.stackoverflow.co/2025/technology/#databases
    含义:出海项目优先选 PostgreSQL 是踩在趋势上,社区、文档、生态都在增强。
  • Docker Hub 周拉取量:官方 postgres 镜像约 2800 万次/周,mysql 约 740 万次/周,比例约 3.8:1(2026 年初数据)。
    来源:https://vonng.com/en/pg/pg-vs-mysql-2026/
    含义:镜像拉取频次直接反映生产部署活跃度,PostgreSQL 在 Docker 生态里已经是事实标准。
  • MySQL 8.4 认证插件变化:默认认证插件从 mysql_native_password 改为 caching_sha2_password,连接驱动要同步到 8.x。
    来源:https://dev.mysql.com/doc/refman/8.4/en/upgrading-from-previous-series.html
    含义:老代码连 MySQL 8.4 报错,先升级驱动再升级数据库。
  • Redis 8.2 GA(2026 年):核心命令执行速度提升 35%,JSON 处理内存开销降低 67%。
    来源:https://redis.io/blog/redis-8-2-ga/
    含义:Redis 8.2 是 2026 年主力版本,配合 redis-stack 或 RediSearch 还能跑向量搜索。
  • Docker 命名卷 vs Bind Mount 性能:Linux 上命名卷用本地卷驱动(local driver)性能与 Bind Mount 接近,跨平台无显著差异;Windows/Mac 上命名卷走 virtiofs,比旧版挂载快 2-5 倍。
    来源:https://docs.docker.com/storage/volumes/
    含义:生产部署用命名卷,开发环境追求实时同步用 Bind Mount。

💡 透明声明:本文包含 7 处推广链接(4 个独立服务,如 Vultr / Cloudflare / Tailscale 等)。每次点击我会收到一笔佣金,不影响你的购买价格,也不影响我的内容判断,工具好不好用我自己用了才知道。完整推广政策见 ittinker.com/about。

底层机制与架构拆解:数据与引用 四阶段递进分析模型 · 从前置识别到最终工程定级 S1 PostgreSQL开发者采… (2025 Stack Overflow 全球调研,49000+开发… S2 Docker Hub周拉取量 官方postgres镜像约 2800万次/周,mysql约… S3 MySQL 8.4认证插件变… 默认认证插件从 mysqlnativepassword… S4 Redis 8.2 GA(2026… 核心命令执行速度提升 35%,JSON处理内存开销… 💡 工程启示:任何一个前置环节失守,都会通过传导链放大为下游的系统性偏差

💼 推荐工具


本文最后更新:2026年8月

独立开发者工具箱系列,欢迎关注!

如果这篇文章对你有帮助,支持一下让我更有动力写下去。

问题整改前后解剖:💼推荐工具 ⚠️ 整改前:常见踩坑与失分现场 • 盲区 1:忽视「Vultr」边界约束 • 盲区 2:缺乏「Hetzner Cloud」实测核验 • 盲区 3:遗漏「DigitalOcean Managed…」兜底监控 后果:线上隐性故障频发 / 评估等级下调 ✅ 整改后:高确定性工程实践 • 动作 1:Vultr:海外VPS,部署Docker数据库推荐4G起… • 动作 2:Hetzner Cloud:欧洲高性价比,4G内存CCX约… • 动作 3:DigitalOcean Managed Databases:不想自… 收益:指标可观测、配置可复现、风险可阻断