搭建开发环境是新人的第一道坎。本文记录用 Docker + AI 脚本将整个流程自动化,包含依赖安装、数据库初始化、配置文件生成,并适配 macOS/Linux/Windows 三平台。新人从对着文档折腾两天,变成跑一条命令、两小时后开始写代码。

事情是怎么开始的
上个月团队入职了一位新同事。按照惯例,我发给他一份《开发环境搭建指南》------一份 6 页的 Notion 文档,包含:
- Node.js 18 + pnpm 安装
- MySQL 8.0 + Redis 7 本地安装
- 三个私有 npm 包的认证配置
- hosts 文件和本地 DNS 配置
- 自签证书安装
- 五个微服务的 .env 文件配置
第一天,他在装 MySQL 的时候遇到了版本冲突。第二天,他在配私有 npm 包的时候认证失败。第三天早上,他终于跑通了第一个服务,但第二个服务报错------有个环境变量写错了。
我看着他,想起自己两年前入职时同样折腾了三天。这个行业对新人的欢迎方式,为什么总是从折磨开始?
我花了一个周末,把整个环境配置流程做成了自动化。新同事成了这套方案的第一个用户,从拿到电脑到提交第一行代码,用了不到一个上午。
先分析:为什么环境配置这么容易崩
我把那份 6 页的文档拆解了一遍,发现环境配置的坑集中在三个地方:
| 问题类型 | 典型表现 | 根本原因 |
|---|---|---|
| 版本不一致 | "我这跑不起来" "我这能跑啊" | 文档写的是推荐版本,不是强制版本 |
| 环境差异 | macOS 能跑,Windows 报错 | 路径分隔符、换行符、依赖编译差异 |
| 隐式依赖 | 文档没写,但实际需要 | 老员工知道,但忘了写进文档 |
第一个问题好解决:用 Docker 固定版本。第二个问题需要适配脚本。第三个问题最麻烦------如何把老员工的"肌肉记忆"挖出来?
我想到的办法是:让 AI 审查项目代码,找出所有隐式的环境依赖。这是后话,先说方案。
整体方案:一条命令拉起全部服务
最终搭建的 setup 命令,背后做了这些事:
arduino
setup
├── 1. 检查 Docker / Node.js / Git 是否已安装
├── 2. 拉取项目代码(含子模块)
├── 3. 自动生成 .env 文件(交互式提问)
├── 4. 拉取 Docker 镜像(MySQL / Redis / MinIO)
├── 5. 初始化数据库(建表 + 种子数据)
├── 6. 安装 npm 依赖(含私有包认证)
├── 7. 配置 hosts + 自签证书
├── 8. 启动所有微服务
└── 9. 健康检查
核心是一个 Shell 脚本,兼容 macOS、Linux 和 WSL2(Windows 用户统一用 WSL2,省去维护 PowerShell 版本的成本)。
关键模块的实现细节
1. 环境检测:提前报错,而不是跑到一半再崩
bash
#!/bin/bash
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
check_command() {
if ! command -v "$1" &> /dev/null; then
echo -e "${RED}❌ $1 未安装,请先安装 $2${NC}"
exit 1
fi
echo -e "${GREEN}✓${NC} $1 已安装: $($1 --version 2>&1 | head -n1)"
}
echo "=== 环境检测 ==="
check_command docker "Docker Desktop (https://docker.com)"
check_command node "Node.js 18+ (https://nodejs.org)"
check_command git "Git (https://git-scm.com)"
check_command pnpm "pnpm (npm install -g pnpm)"
# 检查 Docker 是否在运行
if ! docker info &> /dev/null; then
echo -e "${RED}❌ Docker 未运行,请启动 Docker Desktop${NC}"
exit 1
fi
# 检查 Node.js 版本
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
if [ "$NODE_VERSION" -lt 18 ]; then
echo -e "${RED}❌ Node.js 版本过低 ($(node -v)),需要 18+${NC}"
exit 1
fi
做这一步的原因:过去很多环境问题都是在安装到一半时报错,前置检查可以一次性告诉用户"你需要装什么",而不是让它装到第三步再崩、然后倒回去查原因。
2. .env 文件生成:用交互式提问代替手写
过去新同事经常在 .env 里填错值,比如把生产环境的数据库端口写进本地。我写了个引导式脚本,逐项提问并校验格式:
bash
generate_env() {
echo ""
echo "=== 配置环境变量 ==="
# 检测本机 IP 作为默认值
LOCAL_IP=$(ipconfig getifaddr en0 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}')
read -p "数据库主机地址 [默认: 127.0.0.1]: " DB_HOST
DB_HOST=${DB_HOST:-127.0.0.1}
read -p "数据库端口 [默认: 3306]: " DB_PORT
DB_PORT=${DB_PORT:-3306}
# 端口格式校验
if ! [[ "$DB_PORT" =~ ^[0-9]+$ ]] || [ "$DB_PORT" -lt 1 ] || [ "$DB_PORT" -gt 65535 ]; then
echo -e "${RED}端口格式错误${NC}"
return 1
fi
read -p "Redis 端口 [默认: 6379]: " REDIS_PORT
REDIS_PORT=${REDIS_PORT:-6379}
read -p "本地服务域名 [默认: dev.local]: " DEV_DOMAIN
DEV_DOMAIN=${DEV_DOMAIN:-dev.local}
# 生成 .env 文件
cat > .env << EOF
# 由 setup 脚本自动生成
DB_HOST=$DB_HOST
DB_PORT=$DB_PORT
DB_USER=dev_user
DB_PASS=dev_password_123
DB_NAME=myapp_dev
REDIS_HOST=127.0.0.1
REDIS_PORT=$REDIS_PORT
DEV_DOMAIN=$DEV_DOMAIN
EOF
echo -e "${GREEN}✓ .env 文件已生成${NC}"
}
3. 用 Docker Compose 统一中间件版本
这是解决"版本不一致"的核心。不再要求同事手动安装 MySQL 和 Redis,而是用容器固定版本:
yaml
# docker-compose.dev.yml
version: '3.8'
services:
mysql:
image: mysql:8.0.35
container_name: myapp-mysql-dev
ports:
- "${DB_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: root_123
MYSQL_DATABASE: myapp_dev
MYSQL_USER: dev_user
MYSQL_PASSWORD: dev_password_123
volumes:
- mysql_data:/var/lib/mysql
- ./init-scripts:/docker-entrypoint-initdb.d # 自动初始化
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7.2-alpine
container_name: myapp-redis-dev
ports:
- "${REDIS_PORT:-6379}:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
volumes:
mysql_data:
重点 :healthcheck 配置让后续脚本可以等待服务就绪后再执行,避免"数据库还没起来就开始建表"的竞态问题。
4. 数据库初始化:自动建表 + 种子数据
bash
wait_for_mysql() {
echo "等待 MySQL 就绪..."
until docker exec myapp-mysql-dev mysqladmin ping -h localhost --silent; do
sleep 2
done
echo -e "${GREEN}✓ MySQL 已就绪${NC}"
}
init_database() {
wait_for_mysql
# 从源码中的 migration 文件自动建表
for f in ./migrations/*.up.sql; do
echo "执行: $f"
docker exec -i myapp-mysql-dev mysql -u dev_user -pdev_password_123 myapp_dev < "$f"
done
# 插入种子数据(可选)
if [ -f ./seeds/dev.sql ]; then
read -p "是否插入测试数据?[Y/n]: " insert_seed
if [[ "$insert_seed" =~ ^[Yy]?$ ]]; then
docker exec -i myapp-mysql-dev mysql -u dev_user -pdev_password_123 myapp_dev < ./seeds/dev.sql
echo -e "${GREEN}✓ 测试数据已插入${NC}"
fi
fi
}
5. 私有 npm 包认证:把繁琐的配置变成一条命令
bash
setup_npm_auth() {
echo ""
echo "=== 配置私有 npm 包访问 ==="
if [ -z "${NPM_TOKEN:-}" ]; then
echo -e "${YELLOW}需要 GitHub 个人访问令牌来安装私有包${NC}"
echo "获取方式: GitHub Settings → Developer settings → Personal access tokens"
echo "需要 read:packages 权限"
read -s -p "粘贴你的 token: " NPM_TOKEN
echo ""
fi
# 写入 .npmrc
cat > ~/.npmrc << EOF
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
@mycompany:registry=https://npm.pkg.github.com/
EOF
echo -e "${GREEN}✓ npm 认证配置完成${NC}"
}
6. hosts 和自签证书:让本地域名可访问
bash
setup_hosts_and_certs() {
DOMAIN=${DEV_DOMAIN:-dev.local}
# 添加 hosts 记录
if ! grep -q "$DOMAIN" /etc/hosts; then
echo "127.0.0.1 $DOMAIN" | sudo tee -a /etc/hosts > /dev/null
echo -e "${GREEN}✓ hosts 已添加: $DOMAIN → 127.0.0.1${NC}"
fi
# 生成自签证书
if [ ! -f ./certs/$DOMAIN.crt ]; then
mkdir -p ./certs
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout ./certs/$DOMAIN.key \
-out ./certs/$DOMAIN.crt \
-subj "/CN=$DOMAIN" \
-addext "subjectAltName=DNS:$DOMAIN" 2>/dev/null
echo -e "${GREEN}✓ 自签证书已生成${NC}"
# macOS 信任证书
if [[ "$OSTYPE" == "darwin"* ]]; then
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ./certs/$DOMAIN.crt 2>/dev/null || true
fi
fi
}
用 AI 找出文档里没写的隐式依赖
这部分是意外收获。我在让 AI 审查项目代码时,专门问了这个问题:
提示词:
diff
审查这个项目的代码,找出所有隐式的环境依赖。
"隐式依赖"指代码中硬编码的、但开发环境搭建文档中没有提到的依赖,比如:
- 特定版本的编译工具(gcc、python3 等)
- 全局安装的 CLI 工具
- 特定的文件路径或端口
- 需要提前创建的目录
请列出每一项并给出代码中的证据。
AI 返回了几个我根本没想到的点:
- 一个 PDF 生成服务依赖
chromium二进制文件,通过puppeteer调用。文档里完全没提。 - 一个图片处理脚本内部调用了
sharp,需要系统级libvips库。macOS 上能跑,但 Linux 需要单独安装。 - 某段测试代码硬编码了
/tmp/uploads路径,不存在时静默失败。
我把这些发现补充进了 Dockerfile 和 setup 脚本。这是人很难靠回忆穷举、但 AI 可以靠代码扫描补齐的工作。
最终效果
新同事入职时,流程变成了这样:
bash
1. git clone <项目地址> && cd project
2. chmod +x setup.sh && ./setup.sh
3. 按提示输入 3 个配置项(数据库端口、域名、npm token)
4. 喝杯咖啡,等 10 分钟
5. 访问 http://dev.local:3000
第一版用了 15 分钟,新同事反馈了几个交互不友好的地方(比如 token 提示不清楚、没有进度条),我迭代了两次后稳定下来。
对比:
| 指标 | 之前 | 之后 |
|---|---|---|
| 环境搭建时间 | 1~3 天 | 0.5~2 小时 |
| 手动步骤 | 约 40 步 | 3 步 |
| 搭建失败率 | 约 60% | 0%(三次测试) |
| 文档维护 | 手动更新,常过期 | 脚本即文档,不过期 |
踩过的坑
1. Docker 镜像拉取慢
国内拉 Docker Hub 镜像很慢,脚本第一次跑可能卡在 pull 上。解决方案是在脚本开头检测网络,提示配置镜像加速器:
bash
check_docker_mirror() {
if ! docker info | grep -q "Mirrors"; then
echo -e "${YELLOW}⚠ 未检测到 Docker 镜像加速器,拉取镜像可能很慢${NC}"
echo "建议配置: Docker Desktop → Settings → Docker Engine → registry-mirrors"
fi
}
2. Windows 路径问题
WSL2 下 /mnt/c/ 路径的性能很差,脚本检测到 WSL 后会自动把项目 clone 到 ~/ 下而非 /mnt/c/:
bash
if grep -q "microsoft" /proc/version 2>/dev/null; then
echo "检测到 WSL 环境,项目将克隆到 ~/ 目录以获得最佳性能"
cd ~
fi
3. 端口冲突
如果同事本地已经装过 MySQL,3306 端口会被占用。脚本主动检测并提示:
bash
if lsof -i :3306 &> /dev/null; then
echo -e "${YELLOW}⚠ 端口 3306 已被占用${NC}"
read -p "是否使用其他端口?[默认 3307]: " ALT_PORT
DB_PORT=${ALT_PORT:-3307}
fi
为什么用 Shell 而不是更"现代"的方案
有人问为什么不直接用 Dev Containers 或者 Nix。原因很简单:
- Dev Containers 需要同事先学会 VS Code 的容器模式,增加了学习成本。
- Nix 很强大,但我们的同事不一定愿意花时间学一个新工具链。
- Shell 脚本:所有 Unix 系统都自带,零额外依赖,出问题可以直接看源码改。
工具选型的原则不是"最先进",而是"让目标用户最容易上手"。
文中使用的 Claude API Key 来自 gpt108.com(该渠道提供 ChatGPT Plus、Claude Pro、Gemini Advanced、Cursor Pro 等账号及 API 充值)。笔者团队生产环境已稳定使用 3 个月,仅作技术方案记录
你们团队的开发环境是怎么搭的?新人入职需要多久才能开始写代码?有什么独特的解决方案?欢迎评论区聊聊。