从零上线一个项目:Docker 部署流程、Compose 编排与完整源码示例
Docker 部署完整教程|附源码
在现代软件开发中,Docker 已经成为部署应用的事实标准之一。无论是个人项目、企业级微服务,还是前后端分离系统,Docker 都能帮助我们快速构建一致、可迁移、易维护的运行环境。
本文将从零开始,带你完整了解 Docker 的部署流程,包括 Docker 基础概念、环境安装、项目准备、Dockerfile 编写、镜像构建、容器运行、Docker Compose 编排、常见部署优化以及完整示例源码。即使你之前没有 Docker 经验,也可以按照本文步骤完成一个可运行项目的容器化部署。
一、为什么要使用 Docker?
在没有 Docker 之前,我们部署项目通常需要手动安装各种环境,例如:
- JDK
- Node.js
- Python
- MySQL
- Redis
- Nginx
- 各种系统依赖库
不同服务器之间的环境可能存在差异,比如本地开发使用的是 Node.js 18,而服务器上是 Node.js 16;本地 MySQL 是 8.0,服务器上却是 5.7。这类环境不一致问题经常导致“我本地能跑,服务器不能跑”。
Docker 的核心价值就是解决环境一致性问题。
使用 Docker 后,我们可以把应用程序、运行环境、依赖配置统一打包成镜像,然后在任意支持 Docker 的机器上运行。这样部署就变成了:
docker run your-image
相比传统部署方式,Docker 具有以下优势:
-
环境一致
开发、测试、生产环境可以保持高度一致,减少环境差异导致的问题。 -
部署简单
通过镜像即可快速启动应用,不需要在服务器上反复安装依赖。 -
易于迁移
镜像可以推送到镜像仓库,在不同服务器之间快速拉取运行。 -
隔离性强
每个容器都有独立的运行环境,多个应用之间互不影响。 -
便于扩展
配合 Docker Compose、Kubernetes 等工具,可以方便地管理多容器应用。
二、Docker 核心概念
在正式部署之前,需要先理解几个常见概念。
1. 镜像 Image
镜像可以理解为应用运行环境的模板。它包含了应用程序代码、运行时环境、依赖库、配置文件等内容。
例如:
nginx:latest
mysql:8.0
redis:7
node:18-alpine
这些都是常见的 Docker 镜像。
2. 容器 Container
容器是镜像运行后的实例。镜像类似于类,容器类似于对象。
一个镜像可以创建多个容器,例如同一个 Nginx 镜像可以运行多个 Nginx 服务实例。
3. Dockerfile
Dockerfile 是用来构建镜像的文本文件。它描述了镜像从基础环境到最终应用的构建过程。
例如:
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
4. Docker Compose
Docker Compose 用于管理多个容器。比如一个项目需要同时运行:
- 后端服务
- MySQL
- Redis
- Nginx
如果逐个使用 docker run 启动会比较麻烦,而 Docker Compose 可以通过一个 docker-compose.yml 文件统一管理。
三、安装 Docker
以下以 Linux 服务器为例,推荐使用 Ubuntu 或 CentOS。
1. Ubuntu 安装 Docker
更新软件源:
sudo apt update
安装依赖:
sudo apt install -y ca-certificates curl gnupg lsb-release
安装 Docker:
curl -fsSL https://get.docker.com | bash
启动 Docker:
sudo systemctl start docker
设置开机自启:
sudo systemctl enable docker
查看 Docker 版本:
docker -v
如果输出类似内容,说明安装成功:
Docker version 26.1.0, build xxxxx
2. CentOS 安装 Docker
安装依赖:
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
安装 Docker:
curl -fsSL https://get.docker.com | bash
启动 Docker:
sudo systemctl start docker
设置开机自启:
sudo systemctl enable docker
查看状态:
sudo systemctl status docker
四、准备示例项目源码
为了让教程更完整,下面我们使用一个简单的 Node.js Web 项目作为示例。该项目提供一个 HTTP 接口,访问后返回一段 JSON 数据。
项目结构如下:
docker-demo
├── app.js
├── package.json
├── Dockerfile
├── .dockerignore
└── docker-compose.yml
五、编写 Node.js 示例源码
1. app.js
const express = require("express");
const app = express();
const PORT = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.json({
code: 200,
message: "Docker 部署成功!",
time: new Date().toISOString()
});
});
app.get("/health", (req, res) => {
res.json({
status: "UP",
service: "docker-demo"
});
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
这个文件创建了一个 Express 服务,提供两个接口:
/:返回部署成功信息/health:健康检查接口
2. package.json
{
"name": "docker-demo",
"version": "1.0.0",
"description": "Docker deployment demo project",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.18.3"
}
}
六、编写 Dockerfile
在项目根目录下创建 Dockerfile 文件:
FROM node:18-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
下面逐行解释:
1. 指定基础镜像
FROM node:18-alpine
表示使用 Node.js 18 的 Alpine 版本作为基础镜像。Alpine 是一个轻量级 Linux 发行版,镜像体积较小,适合生产部署。
2. 设置工作目录
WORKDIR /app
容器内部的工作目录设置为 /app,后续命令都会在该目录下执行。
3. 复制依赖声明文件
COPY package.json ./
先复制 package.json,这样可以利用 Docker 构建缓存。只要依赖文件不变,后续构建时 npm install 可以直接复用缓存。
4. 安装生产依赖
RUN npm install --production
只安装生产环境依赖,避免安装开发依赖,减少镜像体积。
5. 复制项目源码
COPY . .
将当前目录下的项目代码复制到容器中的 /app 目录。
6. 暴露端口
EXPOSE 3000
声明容器内部服务监听的是 3000 端口。
7. 启动命令
CMD ["npm", "start"]
容器启动时执行 npm start。
七、编写 .dockerignore
.dockerignore 用于排除不需要复制到镜像中的文件,类似 .gitignore。
node_modules
npm-debug.log
Dockerfile
docker-compose.yml
.git
.gitignore
README.md
这样可以避免将无用文件打包进镜像,提高构建速度,减小镜像体积。
八、构建 Docker 镜像
进入项目目录:
cd docker-demo
执行构建命令:
docker build -t docker-demo:1.0 .
参数说明:
docker build:构建镜像-t docker-demo:1.0:指定镜像名称和版本标签.:表示 Dockerfile 位于当前目录
构建完成后,查看本地镜像:
docker images
你应该可以看到类似内容:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-demo 1.0 xxxxxxxx 1 minute ago 180MB
九、运行 Docker 容器
执行以下命令启动容器:
docker run -d \
--name docker-demo-app \
-p 3000:3000 \
docker-demo:1.0
参数说明:
-d:后台运行容器--name docker-demo-app:指定容器名称-p 3000:3000:端口映射,宿主机 3000 端口映射到容器 3000 端口docker-demo:1.0:要运行的镜像
查看正在运行的容器:
docker ps
访问接口:
curl http://localhost:3000
返回结果类似:
{
"code": 200,
"message": "Docker 部署成功!",
"time": "2026-01-01T12:00:00.000Z"
}
如果你是在云服务器上部署,需要确认安全组已经放行 3000 端口,然后通过浏览器访问:
http://服务器公网IP:3000
十、常用容器管理命令
1. 查看运行中的容器
docker ps
2. 查看所有容器
docker ps -a
3. 查看容器日志
docker logs docker-demo-app
实时查看日志:
docker logs -f docker-demo-app
4. 停止容器
docker stop docker-demo-app
5. 启动已停止的容器
docker start docker-demo-app
6. 重启容器
docker restart docker-demo-app
7. 删除容器
docker rm docker-demo-app
如果容器正在运行,需要先停止,或者强制删除:
docker rm -f docker-demo-app
8. 删除镜像
docker rmi docker-demo:1.0
十一、使用 Docker Compose 部署
单个容器可以用 docker run,但实际项目中通常不止一个服务。为了更方便管理,我们可以使用 Docker Compose。
在项目根目录创建 docker-compose.yml:
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
image: docker-demo:1.0
container_name: docker-demo-app
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
restart: always
启动服务:
docker compose up -d
如果你的 Docker 版本较旧,可能需要使用:
docker-compose up -d
查看服务状态:
docker compose ps
查看日志:
docker compose logs -f
停止服务:
docker compose down
重新构建并启动:
docker compose up -d --build
十二、添加 Nginx 反向代理
在生产环境中,我们一般不会直接暴露 Node.js 应用端口,而是通过 Nginx 做反向代理。
可以将应用监听在内部端口,例如 3000,然后由 Nginx 对外提供 80 或 443 端口。
扩展后的项目结构如下:
docker-demo
├── app.js
├── package.json
├── Dockerfile
├── .dockerignore
├── docker-compose.yml
└── nginx
└── default.conf
1. nginx/default.conf
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
其中:
proxy_pass http://app:3000;
这里的 app 是 Docker Compose 中的服务名称。Compose 会自动创建网络,服务之间可以通过服务名互相访问。
2. 修改 docker-compose.yml
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
image: docker-demo:1.0
container_name: docker-demo-app
environment:
- NODE_ENV=production
- PORT=3000
restart: always
nginx:
image: nginx:1.25-alpine
container_name: docker-demo-nginx
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
restart: always
启动:
docker compose up -d --build
此时访问:
http://服务器公网IP
就会通过 Nginx 转发到 Node.js 应用。
十三、部署带 MySQL 的应用示例
如果你的应用依赖数据库,可以在 Compose 中加入 MySQL 服务。
示例:
version: "3.8"
services:
app:
build: .
image: docker-demo:1.0
container_name: docker-demo-app
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=123456
- DB_NAME=demo
depends_on:
- mysql
restart: always
mysql:
image: mysql:8.0
container_name: docker-demo-mysql
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=demo
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
restart: always
volumes:
mysql_data:
这里使用了 Docker Volume:
volumes:
mysql_data:
它的作用是持久化 MySQL 数据。否则容器删除后,数据库数据也可能丢失。
生产环境中不建议直接将 MySQL 暴露到公网。如果必须开放端口,应限制访问来源 IP,并设置复杂密码。
十四、生产环境部署建议
1. 不要使用 latest 标签
很多初学者喜欢使用:
node:latest
mysql:latest
nginx:latest
但生产环境不推荐这样做。因为 latest 会随着官方镜像更新而变化,可能导致重新部署时出现兼容性问题。
推荐指定明确版本:
FROM node:18.20-alpine
或:
image: mysql:8.0
2. 使用 restart 策略
生产环境建议配置:
restart: always
这样容器异常退出后会自动重启。
常见策略包括:
restart: no
restart: always
restart: unless-stopped
restart: on-failure
3. 配置健康检查
可以在 docker-compose.yml 中添加健康检查:
services:
app:
build: .
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
健康检查可以帮助我们判断容器内服务是否真正可用。
4. 使用环境变量管理配置
不要将数据库密码、Token、密钥直接写死在代码中。推荐通过环境变量传入:
environment:
- DB_PASSWORD=${DB_PASSWORD}
然后创建 .env 文件:
DB_PASSWORD=your_strong_password
注意 .env 文件不要提交到 Git 仓库。
5. 控制镜像体积
镜像越小,传输和部署速度越快,也能减少潜在安全风险。
优化方式包括:
- 使用 alpine 版本基础镜像
- 使用
.dockerignore - 删除不必要文件
- 多阶段构建
- 只安装生产依赖
十五、使用多阶段构建优化镜像
如果是前端项目或需要编译的后端项目,可以使用多阶段构建。
以下是一个前端 Vue/React 项目构建示例:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
这个 Dockerfile 分为两个阶段:
-
builder 阶段
使用 Node.js 安装依赖并构建前端静态文件。 -
运行阶段
使用 Nginx 作为最终运行环境,只保留构建后的静态文件。
这样最终镜像不包含 Node.js、源码和开发依赖,体积更小,也更安全。
十六、镜像推送到 Docker Hub
如果你希望在其他服务器部署,可以将镜像推送到 Docker Hub 或私有镜像仓库。
1. 登录 Docker Hub
docker login
输入用户名和密码。
2. 给镜像打标签
假设你的 Docker Hub 用户名是 yourname:
docker tag docker-demo:1.0 yourname/docker-demo:1.0
3. 推送镜像
docker push yourname/docker-demo:1.0
4. 在服务器拉取镜像
docker pull yourname/docker-demo:1.0
5. 运行镜像
docker run -d \
--name docker-demo-app \
-p 3000:3000 \
yourname/docker-demo:1.0
十七、完整源码汇总
下面给出完整可运行源码。
1. app.js
const express = require("express");
const app = express();
const PORT = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.json({
code: 200,
message: "Docker 部署成功!",
time: new Date().toISOString()
});
});
app.get("/health", (req, res) => {
res.json({
status: "UP",
service: "docker-demo"
});
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
2. package.json
{
"name": "docker-demo",
"version": "1.0.0",
"description": "Docker deployment demo project",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.18.3"
}
}
3. Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
4. .dockerignore
node_modules
npm-debug.log
Dockerfile
docker-compose.yml
.git
.gitignore
README.md
5. docker-compose.yml
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
image: docker-demo:1.0
container_name: docker-demo-app
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
restart: always
6. 带 Nginx 的 docker-compose.yml
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
image: docker-demo:1.0
container_name: docker-demo-app
environment:
- NODE_ENV=production
- PORT=3000
restart: always
nginx:
image: nginx:1.25-alpine
container_name: docker-demo-nginx
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
restart: always
7. nginx/default.conf
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
十八、常见问题排查
1. 端口被占用
如果启动容器时报错:
Bind for 0.0.0.0:3000 failed: port is already allocated
说明宿主机 3000 端口已经被占用。
可以查看端口占用:
sudo lsof -i:3000
或修改端口映射:
docker run -d -p 3001:3000 docker-demo:1.0
此时访问:
http://服务器IP:3001
2. 容器启动后马上退出
查看日志:
docker logs docker-demo-app
常见原因包括:
- 启动命令错误
- 依赖安装失败
- 环境变量缺失
- 端口配置错误
- 代码运行异常
3. 修改代码后不生效
如果代码已经修改,但容器中没有变化,需要重新构建镜像:
docker compose up -d --build
或者:
docker build -t docker-demo:1.0 .
docker rm -f docker-demo-app
docker run -d --name docker-demo-app -p 3000:3000 docker-demo:1.0
4. 无法从外网访问
请检查以下内容:
- 容器是否正常运行:
docker ps
- 端口是否映射正确:
docker port docker-demo-app
- 服务器防火墙是否放行端口:
sudo ufw allow 3000
- 云服务器安全组是否开放对应端口。
十九、推荐部署流程
一个比较规范的 Docker 部署流程如下:
本地开发代码
↓
编写 Dockerfile
↓
编写 docker-compose.yml
↓
本地构建并测试
↓
提交代码到 Git 仓库
↓
服务器拉取代码
↓
执行 docker compose up -d --build
↓
配置 Nginx 和域名
↓
配置 HTTPS
↓
上线运行
如果项目规模较大,可以进一步接入 CI/CD,例如 GitHub Actions、GitLab CI、Jenkins 等,实现代码提交后自动构建镜像、推送镜像、远程部署。
二十、总结
本文完整介绍了 Docker 部署应用的基本流程,从 Docker 的核心概念讲起,依次完成了环境安装、示例项目编写、Dockerfile 编写、镜像构建、容器运行、Docker Compose 编排、Nginx 反向代理、MySQL 扩展、镜像推送以及常见问题排查。
对于大多数中小型项目来说,掌握以下几个文件就已经可以完成 Docker 化部署:
Dockerfile.dockerignoredocker-compose.ymlnginx/default.conf
其中,Dockerfile 负责构建应用镜像,docker-compose.yml 负责管理服务编排,Nginx 用于统一入口和反向代理。通过 Docker,我们可以让部署过程更加标准化、自动化和可复制。
如果你是第一次接触 Docker,建议先从本文的 Node.js 示例项目开始练习,理解镜像构建和容器运行的基本逻辑;随后再尝试加入 MySQL、Redis、Nginx 等组件,逐步过渡到真实生产环境部署。