Mac mini 折腾记(五):用 Docker 容器化所有数据库,彻底告别安装地狱

Mac mini 折腾记(五):用 Docker 容器化所有数据库,彻底告别安装地狱

上一篇文章里,我用 mise 统一管理了所有编程语言的版本,系统盘保持干净,所有数据都在外置硬盘。

现在开发环境有了,但还缺最重要的一环:数据库。

Windows 上的数据库噩梦

在 Windows 上,我装了 MySQL、Redis、MongoDB、PostgreSQL。

每个数据库都是独立安装的:

  • MySQL 装在 D 盘,配置文件在 C 盘
  • Redis 装在 D 盘,但没有配置成服务,每次都要手动启动
  • MongoDB 装在 C 盘,占了好几 GB
  • PostgreSQL 装在 D 盘,但数据目录在 C 盘

这些数据库的问题是:

  1. 占用系统盘空间:数据目录散落在各个地方
  2. 版本管理混乱:想升级 MySQL,要先卸载旧版本,再装新版本
  3. 开机自启动:有些数据库会开机自启动,拖慢系统
  4. 端口冲突:有时候忘记关 MySQL,再装一个测试版本,端口就冲突了

最要命的是,如果我想在另一台电脑上搭建同样的环境,我得重新安装一遍所有数据库,配置一遍所有参数。

这次换 Mac,我不想重蹈覆辙。我要用 Docker 容器化所有数据库。

为什么选择 Docker?

Docker 的好处很明显:

1. 隔离性

  • 每个数据库运行在独立的容器里
  • 不会互相干扰
  • 不会污染系统环境

2. 版本管理方便

  • 想用 MySQL 8.0?拉取 mysql:8.0 镜像
  • 想用 MySQL 5.7?拉取 mysql:5.7 镜像
  • 两个版本可以同时运行(只要端口不冲突)

3. 数据持久化

  • 容器删除了,数据还在
  • 数据存储在外置硬盘,系统盘不占用

4. 易于迁移

  • 换电脑?复制一个 docker-compose.yml 文件就行
  • 所有配置都在文件里,不需要重新安装

5. 统一管理

  • 一个命令启动所有数据库
  • 一个命令停止所有数据库
  • 一个命令查看所有数据库的状态

Docker Desktop 还是 OrbStack?

在 Windows 上,Docker Desktop 是唯一选择。但在 Mac 上,我发现了一个更好的选择:OrbStack

我在网上搜索 “Mac Docker”,看到很多人推荐 OrbStack。我仔细对比了一下:

Docker Desktop 的问题:

  • 占用资源大(空载占用 2GB 内存)
  • 启动慢(需要 30 秒)
  • 文件系统性能差(Mac 和容器之间的文件共享很慢)
  • 后台运行时会拖慢系统

OrbStack 的优势:

  • 占用资源小(空载只占用 200MB 内存)
  • 启动快(只需要 2 秒)
  • 文件系统性能好(快 5-10 倍)
  • 后台运行几乎不占用 CPU
  • 完全兼容 Docker 命令和 docker-compose

看到这些对比,我毫不犹豫选择了 OrbStack。

安装 OrbStack

安装很简单:

1
brew install orbstack

安装完成后,打开 OrbStack 应用。第一次打开时,它会自动配置 Docker 环境,在菜单栏显示一个图标。

我试着运行一个测试容器:

1
docker run hello-world

输出:

1
2
Hello from Docker!
This message shows that your installation appears to be working correctly.

成功了!Docker 环境已经配置好了。

第一个坑:镜像下载太慢

我试着拉取 MySQL 镜像:

1
docker pull mysql:8.0

结果卡在那里,半天没动静。

我意识到问题了:Docker 默认从 Docker Hub 下载镜像,但 Docker Hub 在国内访问很慢,甚至经常超时。

我需要配置国内镜像源。

配置 OrbStack 镜像加速

打开 OrbStack 的设置(点击菜单栏图标,选择 Settings),找到 Docker Engine 配置。

在配置文件里添加国内镜像源:

1
2
3
4
5
6
7
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://mirror.ccs.tencentyun.com"
]
}

保存后,重启 OrbStack。

再次拉取镜像:

1
docker pull mysql:8.0

这次速度快多了,几分钟就下载完了。

这是第一个坑:OrbStack 必须配置国内镜像源,否则下载镜像会非常慢。

迁移数据目录到外置硬盘

OrbStack 默认把所有数据(镜像、容器、卷)存储在系统盘的 ~/Library/Containers/com.orbstack.OrbStack/Data/ 目录。

对于 256G 的系统盘来说,这不行。我需要把数据目录迁移到外置硬盘。

迁移步骤

第一步:停止 OrbStack

点击菜单栏的 OrbStack 图标,选择 “Quit OrbStack”。

第二步:创建外置硬盘目录

1
mkdir -p /Volumes/DataDisk/DevEnv/orbstack

第三步:移动数据目录

1
2
3
4
5
# 移动数据目录到外置硬盘
mv ~/Library/Containers/com.orbstack.OrbStack/Data /Volumes/DataDisk/DevEnv/orbstack/

# 创建符号链接
ln -s /Volumes/DataDisk/DevEnv/orbstack/Data ~/Library/Containers/com.orbstack.OrbStack/Data

第四步:重新启动 OrbStack

打开 OrbStack 应用,它会自动使用外置硬盘的数据目录。

第五步:验证

1
2
3
4
# 查看符号链接
ls -lh ~/Library/Containers/com.orbstack.OrbStack/Data

# 输出应该显示这是一个符号链接,指向外置硬盘

如果输出类似这样:

1
lrwxr-xr-x  1 prorise  staff    48B Jan 30 16:00 Data -> /Volumes/DataDisk/DevEnv/orbstack/Data

说明迁移成功。

现在,所有 Docker 的数据都会存储在外置硬盘,系统盘一点都不占用。

规划目录结构

在开始配置数据库之前,我先规划了一下目录结构。

我在外置硬盘的 DevEnv 目录下,创建了一个 docker-compose 目录,专门存放 docker-compose 项目:

1
2
mkdir -p /Volumes/DataDisk/DevEnv/docker-compose/databases
cd /Volumes/DataDisk/DevEnv/docker-compose/databases

然后创建各个数据库的数据目录:

1
2
3
4
5
mkdir -p mysql/data mysql/conf
mkdir -p redis/data redis/conf
mkdir -p mongodb/data
mkdir -p postgres/data
mkdir -p minio/data

目录结构变成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/Volumes/DataDisk/DevEnv/
├── mise/ # mise数据目录
├── caches/ # 各种缓存
├── orbstack/ # OrbStack数据目录
└── docker-compose/ # docker-compose项目
└── databases/ # 数据库服务
├── mysql/
│ ├── data/ # MySQL数据
│ └── conf/ # MySQL配置
├── redis/
│ ├── data/ # Redis数据
│ └── conf/ # Redis配置
├── mongodb/
│ └── data/ # MongoDB数据
├── postgres/
│ └── data/ # PostgreSQL数据
└── minio/
└── data/ # MinIO数据

这样一来,所有数据库的数据都集中在一个地方,清晰明了。

创建 docker-compose 配置

Docker Compose 是 Docker 官方的容器编排工具,可以用一个 YAML 文件定义多个容器,然后一键启动。

我需要配置 5 个数据库:MySQL、Redis、MongoDB、PostgreSQL、MinIO。

第一步:创建.env 文件

根据 Docker 的最佳实践,密码等敏感信息不应该直接写在 docker-compose.yml 里,而应该放在 .env 文件里。

我创建了一个 .env 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat > .env << 'EOF'
# MySQL 配置
MYSQL_ROOT_PASSWORD=MySecurePassword123!
MYSQL_DATABASE=dev_db
MYSQL_USER=dev_user
MYSQL_PASSWORD=DevUserPassword123!

# Redis 配置
REDIS_PASSWORD=RedisPassword123!

# MongoDB 配置
MONGO_INITDB_ROOT_USERNAME=admin
MONGO_INITDB_ROOT_PASSWORD=MongoPassword123!

# PostgreSQL 配置
POSTGRES_USER=postgres
POSTGRES_PASSWORD=PostgresPassword123!
POSTGRES_DB=dev_db

# MinIO 配置
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=MinioPassword123!
EOF

这个文件包含了所有数据库的密码。我把密码都改成了强密码(包含大小写字母、数字、特殊字符)。

重要提示: 这个文件不要提交到 Git,要加到 .gitignore 里。

第二步:创建 docker-compose.yml

然后创建主配置文件:

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
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
# MySQL 8.0
mysql:
image: mysql:8.0
container_name: dev-mysql
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Shanghai
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --default-authentication-plugin=mysql_native_password
networks:
- dev-network

# Redis 7
redis:
image: redis:7-alpine
container_name: dev-redis
restart: unless-stopped
ports:
- "6379:6379"
environment:
TZ: Asia/Shanghai
volumes:
- ./redis/data:/data
- ./redis/conf/redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf --requirepass ${REDIS_PASSWORD}
networks:
- dev-network

# MongoDB 7
mongodb:
image: mongo:7
container_name: dev-mongodb
restart: unless-stopped
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
TZ: Asia/Shanghai
volumes:
- ./mongodb/data:/data/db
networks:
- dev-network

# PostgreSQL 16
postgres:
image: postgres:16-alpine
container_name: dev-postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
TZ: Asia/Shanghai
volumes:
- ./postgres/data:/var/lib/postgresql/data
networks:
- dev-network

# MinIO (对象存储)
minio:
image: minio/minio:latest
container_name: dev-minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
TZ: Asia/Shanghai
volumes:
- ./minio/data:/data
command: server /data --console-address ":9001"
networks:
- dev-network

networks:
dev-network:
driver: bridge
EOF

这个配置文件定义了 5 个服务,每个服务都是一个容器。

让我解释一下关键配置:

image:指定使用的镜像。我选择了稳定版本(MySQL 8.0、Redis 7、MongoDB 7、PostgreSQL 16),而不是 latest 标签。因为 latest 标签可能会在更新时引入不兼容的变化。

container_name:容器的名字。我统一用 dev- 前缀,方便识别。

restart: unless-stopped:容器会自动重启,除非手动停止。这样 Mac 重启后,容器会自动启动。

ports:端口映射。格式是 宿主机端口:容器端口。比如 3306:3306 表示把容器的 3306 端口映射到 Mac 的 3306 端口。

environment:环境变量。这里使用 ${变量名} 的语法,从 .env 文件读取值。

volumes:数据卷挂载。格式是 宿主机路径:容器路径。这样容器的数据会持久化到宿主机(也就是外置硬盘)。

networks:所有容器都连接到同一个网络 dev-network,这样容器之间可以互相访问。

第三步:创建 Redis 配置文件

Redis 需要一个配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat > redis/conf/redis.conf << 'EOF'
# Redis 配置文件

# 持久化配置
appendonly yes
appendfilename "appendonly.aof"

# RDB 持久化
save 900 1
save 300 10
save 60 10000

# 最大内存
maxmemory 512mb
maxmemory-policy allkeys-lru

# 日志
loglevel notice
EOF

这个配置启用了 Redis 的持久化功能,数据不会因为容器重启而丢失。

启动所有服务

配置文件都准备好了,现在可以启动服务了。

1
docker-compose up -d

-d 参数表示后台运行。

第一次启动时,Docker 会下载所有镜像。这个过程可能需要几分钟,耐心等待。

下载完成后,容器会自动启动。

查看服务状态:

1
docker-compose ps

输出:

1
2
3
4
5
6
NAME            IMAGE               STATUS          PORTS
dev-minio minio/minio:latest Up 2 minutes 0.0.0.0:9000-9001->9000-9001/tcp
dev-mongodb mongo:7 Up 2 minutes 0.0.0.0:27017->27017/tcp
dev-mysql mysql:8.0 Up 2 minutes 0.0.0.0:3306->3306/tcp
dev-postgres postgres:16-alpine Up 2 minutes 0.0.0.0:5432->5432/tcp
dev-redis redis:7-alpine Up 2 minutes 0.0.0.0:6379->6379/tcp

所有服务都是 Up 状态,说明启动成功了。

验证服务

启动成功后,我逐个验证每个服务是否正常工作。

验证 MySQL

1
docker exec -it dev-mysql mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "SELECT VERSION();"

输出:

1
2
3
4
5
+-------------------------+
| VERSION() |
+-------------------------+
| 8.0.36 |
+-------------------------+

MySQL 正常工作。

验证 Redis

1
docker exec -it dev-redis redis-cli -a ${REDIS_PASSWORD} PING

输出:

1
2
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
PONG

Redis 正常工作。(警告信息可以忽略,这是 Redis 提示在命令行里直接输入密码不安全)

验证 MongoDB

1
docker exec -it dev-mongodb mongosh -u ${MONGO_INITDB_ROOT_USERNAME} -p ${MONGO_INITDB_ROOT_PASSWORD} --eval "db.version()"

输出:

1
7.0.5

MongoDB 正常工作。

验证 PostgreSQL

1
docker exec -it dev-postgres psql -U ${POSTGRES_USER} -c "SELECT version();"

输出:

1
PostgreSQL 16.1 on aarch64-unknown-linux-musl, compiled by gcc ...

PostgreSQL 正常工作。

验证 MinIO

打开浏览器,访问 http://localhost:9001,可以看到 MinIO 的登录页面。

.env 文件里配置的用户名和密码登录,可以看到 MinIO 的控制台。

MinIO 正常工作。

查看磁盘占用

所有服务都启动成功了,我查看了一下磁盘占用情况:

1
du -sh /Volumes/DataDisk/DevEnv/docker-compose/databases/*

输出:

1
2
3
4
5
6
7
12K    mysql/conf
1.2G mysql/data
4K minio/data
144M mongodb/data
4K postgres/data
8K redis/conf
12M redis/data

MySQL 占用了 1.2GB(因为初始化时会创建一些系统表),其他数据库占用很少。

所有数据都在外置硬盘,系统盘一点都没占用。

日常使用

启动所有服务

1
2
cd /Volumes/DataDisk/DevEnv/docker-compose/databases
docker-compose up -d

停止所有服务

1
docker-compose stop

重启某个服务

1
docker-compose restart mysql

查看日志

1
2
3
4
5
# 查看所有服务的日志
docker-compose logs -f

# 查看某个服务的日志
docker-compose logs -f mysql

进入容器

1
2
3
4
5
# 进入 MySQL 容器
docker exec -it dev-mysql bash

# 进入 Redis 容器
docker exec -it dev-redis sh

停止并删除所有容器

1
docker-compose down

注意:这个命令只删除容器,不会删除数据。数据还在 mysql/dataredis/data 等目录里。

连接数据库

现在所有数据库都运行在容器里,我可以用数据库客户端连接它们。

用 Navicat 连接 MySQL

打开 Navicat,新建连接:

  • 主机:localhost
  • 端口:3306
  • 用户名:root
  • 密码:.env 文件里的 MYSQL_ROOT_PASSWORD

连接成功,可以看到 dev_db 数据库。

用 Another Redis Desktop Manager 连接 Redis

打开 Another Redis Desktop Manager,新建连接:

  • 主机:localhost
  • 端口:6379
  • 密码:.env 文件里的 REDIS_PASSWORD

连接成功,可以看到 Redis 的键值对。

用代码连接数据库

在代码里连接数据库,配置和本地安装的数据库一样:

Node.js 连接 MySQL:

1
2
3
4
5
6
7
8
9
10
11
const mysql = require('mysql2');

const connection = mysql.createConnection({
host: 'localhost',
port: 3306,
user: 'root',
password: 'MySecurePassword123!',
database: 'dev_db'
});

connection.connect();

Python 连接 Redis:

1
2
3
4
5
6
7
8
9
10
11
import redis

r = redis.Redis(
host='localhost',
port=6379,
password='RedisPassword123!',
decode_responses=True
)

r.set('key', 'value')
print(r.get('key'))

和本地安装的数据库没有任何区别。

这套方案的优势

折腾了一下午,终于把所有数据库都容器化了。回顾一下这套方案的优势:

1. 系统盘干净

所有数据库的数据都在外置硬盘,系统盘只有 OrbStack 本身(几百 MB)。

对于 256G 的系统盘来说,这太重要了。

2. 统一管理

以前我要启动 MySQL,要去 “系统偏好设置” 里找 MySQL,点击 “启动”。

现在只需要一个命令:docker-compose up -d,所有数据库都启动了。

3. 版本管理方便

想用 MySQL 5.7?改一下 docker-compose.yml 里的 image: mysql:5.7,重新启动就行。

想同时运行 MySQL 8.0 和 5.7?复制一份配置,改一下端口,就能同时运行两个版本。

4. 易于迁移

如果以后换电脑,我只需要:

  1. 把外置硬盘插到新电脑
  2. 安装 OrbStack
  3. 运行 docker-compose up -d

所有数据库都能直接用,不需要重新安装、重新配置。

5. 隔离性好

每个数据库运行在独立的容器里,不会互相干扰。

如果某个数据库出问题了,删除容器重新创建就行,不会影响其他数据库。

一些注意事项

这套方案虽然好用,但也有一些需要注意的地方。

1. 外置硬盘必须一直插着

因为所有数据都在外置硬盘,如果硬盘没插,容器启动会失败。

对于 Mac mini 这种台式机,这不是问题,硬盘可以一直插着。

2. 容器重启后 IP 可能变化

虽然端口是固定的(3306、6379 等),但容器的内部 IP 可能会变化。

如果你的代码里用了容器的 IP 地址,可能会出问题。

解决方案是用容器名或 localhost,而不是 IP 地址。

3. 数据备份

虽然数据持久化到外置硬盘了,但还是建议定期备份。

可以用 docker-compose exec 命令导出数据:

1
2
3
4
5
# 导出 MySQL 数据
docker-compose exec mysql mysqldump -uroot -p${MYSQL_ROOT_PASSWORD} dev_db > backup.sql

# 导出 MongoDB 数据
docker-compose exec mongodb mongodump --username ${MONGO_INITDB_ROOT_USERNAME} --password ${MONGO_INITDB_ROOT_PASSWORD} --out /data/backup

4. 性能

容器化会有一点性能损失,但对于开发环境来说,这点损失可以忽略。

如果你对性能要求极高,可以考虑本地安装数据库。但对于大部分开发场景,容器化的性能完全够用。

小结

从 Windows 转到 Mac,数据库的安装配置是一个大挑战。

Windows 上的那套方式(每个数据库独立安装)在 Mac 上也能用,但我不想重复 Windows 上的混乱。

Docker 给了我一个全新的思路:用容器化管理所有数据库。

这套方案不仅适用于 Mac,也适用于 Linux 和 Windows(WSL2)。如果以后我要在服务器上搭建开发环境,也可以用同样的方式。

现在,我的 Mac mini 有了:

  • 统一的语言版本管理(mise)
  • 统一的数据库管理(docker-compose)
  • 所有数据都在外置硬盘
  • 系统盘保持干净

这才是我想要的开发环境。

下一篇文章,我会讲如何配置终端工具(Warp、Starship)和命令行效率工具(bat、fd、ripgrep、fzf),让终端使用体验提升一个档次。