先吐槽一句。以前我部署一个项目,流程是这样的:买服务器、装系统、装nginx、装MySQL、装PHP或者Node、配环境变量、改权限、调配置……一套下来少说俩小时。而且每次换服务器或者帮朋友搭环境,同样的步骤重复一遍,烦得要死。

后来有个同事跟我说,你试试Docker。我说那玩意儿不是大厂才用的吗?他说你学一个下午就懂了。

结果我真花了一个周末,把Docker啃了一遍。从那以后,部署项目就是一条命令的事。今天就把这套东西用大白话讲清楚,不绕弯子。

先搞明白Docker是干啥的

不用背概念。你就这么理解:

以前部署像是搬家——锅碗瓢盆、被子衣服,每样都得单独打包、单独搬运,到了新家还得重新归置。

Docker就像是把整个房间(包括操作系统、软件、配置)直接装进一个集装箱。搬到哪台服务器上,打开就能用,环境一模一样。

所以它解决的核心问题是:环境不一致。本地跑得好好的,上了服务器就报错?Docker能把这个破事干掉。

实战:把一个Node.js项目容器化

我从最简单的例子开始。假设你有一个Express写的API服务,目录结构大概这样:

text

my-app/
├── app.js
├── package.json
└── package-lock.json

第一步:写Dockerfile

在项目根目录新建一个文件,就叫Dockerfile(没有后缀)。内容如下:

dockerfile

# 用官方Node镜像作为基础
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 先复制package文件,这样可以利用缓存
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源代码
COPY . .

# 暴露端口(你的应用监听的端口)
EXPOSE 3000

# 启动命令
CMD ["node", "app.js"]

解释一下:alpine版本很小,最终镜像不到100MB,下载和启动都快。npm cinpm install更适合自动化环境。

第二步:建一个.dockerignore

跟.gitignore一个意思,把不需要打包进镜像的文件排除掉:

text

node_modules
.git
.gitignore
.env
Dockerfile
.dockerignore
logs

第三步:构建镜像

bash

docker build -t my-api:v1 .

第一次会慢一点,因为要下载基础镜像。之后再构建就快了。

第四步:运行容器

bash

docker run -d -p 3000:3000 --name my-api-container my-api:v1

参数说明:-d后台运行,-p 3000:3000把宿主机的3000端口映射到容器的3000端口,--name给容器起个名字。

跑起来之后,访问http://你的服务器IP:3000就能看到服务了。

实战:用docker-compose管理多个服务

单个容器还好说,但真实项目往往需要数据库、缓存、应用一起跑。这时候用docker-compose,一个配置文件全搞定。

在项目根目录新建docker-compose.yml

yaml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - mysql
      - redis
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis
    restart: always

  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=yourpassword
      - MYSQL_DATABASE=myapp
    volumes:
      - mysql-data:/var/lib/mysql
    restart: always

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: always

volumes:
  mysql-data:
  redis-data:

然后启动只需要一条命令:

bash

docker-compose up -d

所有服务一起起来,数据卷自动挂载,重启策略也配好了。停止用docker-compose down

注意volumes那块是持久化数据的关键。如果不挂载,容器删了数据就没了。生产环境一定得挂载。

镜像仓库:把镜像存到云上

镜像做好了,怎么迁移到另一台服务器?不需要重新build,直接把镜像推到仓库。

我一般用阿里云容器镜像服务(免费额度够个人用)。操作步骤:

bash

# 登录仓库
docker login --username=你的账号 registry.cn-hangzhou.aliyuncs.com

# 给镜像打tag
docker tag my-api:v1 registry.cn-hangzhou.aliyuncs.com/你的命名空间/my-api:v1

# 推送
docker push registry.cn-hangzhou.aliyuncs.com/你的命名空间/my-api:v1

然后在另一台服务器上:

bash

docker pull registry.cn-hangzhou.aliyuncs.com/你的命名空间/my-api:v1
docker run -d -p 3000:3000 上面那个镜像名

整个迁移过程,从拉到跑起来,一分钟不到。

生产环境的一些小建议

Docker本身不难,但真正用起来有几个坑,提前说一下。

1. 日志别让它无限增长

容器默认会把所有输出存到本地,跑久了能撑爆磁盘。启动的时候加个参数限制日志大小:

bash

docker run -d --log-opt max-size=10m --log-opt max-file=3 your-image

或者在docker-compose里配置:

yaml

services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

2. 时区问题

很多基础镜像是UTC时间,你打印日志发现比北京时间慢8小时。解决办法:挂载宿主机的时区文件。

yaml

volumes:
  - /etc/localtime:/etc/localtime:ro

或者Dockerfile里设置环境变量:

dockerfile

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

3. 别用root用户跑容器

默认容器里是root,如果被入侵,风险很大。建一个普通用户:

dockerfile

RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs
USER nodejs

4. 更新代码不用重新build整个镜像

如果只是代码变了,不想重新build依赖,可以挂载代码目录。开发环境很方便:

yaml

volumes:
  - ./:/app

但生产环境不建议,直接build新镜像重启最稳妥。

我现在的工作流

经过这几年摸索,我的标准流程是:

  1. 写代码,本地用docker-compose跑起全套环境

  2. 测试通过后,build镜像并推送到仓库(给镜像打上git commit hash作为tag)

  3. 登录生产服务器,docker pull新镜像

  4. 停止旧容器,启动新容器(用同样的数据卷和环境变量)

整个发布过程不到30秒,回滚更简单——把旧tag重新跑起来就行。

说实话,Docker是这几年学的最值的一个工具。以前每次部署都提心吊胆,现在基本不慌。你如果还在手动部署,真心建议花一天时间入门,后面省下的时间不止一百倍。

更多推荐