面向第一次接触 Docker,或想快速捡回基础的人(包含企业级使用)。先跟着一个最小示例走完「写代码 → 构建镜像 → 启动容器 → 访问服务」的链路,再看常用命令和概念。
先建立一张心智图
Docker 帮我们把应用及其运行所需的环境打包起来,让它在不同机器上尽量以一致的方式运行。
应用代码 + Dockerfile
│ docker build
▼
镜像(Image) ── docker run ──▶ 容器(Container)
│
├─ 端口映射:让宿主机能访问容器服务
├─ 挂载:让文件或数据在容器外保存
└─ 网络:让容器之间互相通信
镜像通常从镜像仓库(Registry)下载,也可以构建后推送到仓库。
- 镜像(Image):应用运行所需文件和配置的只读模板。可以理解为一份打包好的“底稿”。
- 容器(Container):镜像启动后的运行实例。一个镜像可以启动多个容器;容器停止不等于镜像消失。
- Dockerfile:描述如何制作镜像的文本文件。
- Docker 客户端与 Docker Engine:我们敲的 docker 命令是客户端;Engine 负责构建镜像、运行容器等工作。
- 镜像仓库(Registry):存放和分发镜像的地方,例如 Docker Hub。
0. 准备环境
安装并启动 Docker Desktop(macOS / Windows)或 Docker Engine(Linux),然后在终端确认:
docker –version
docker info
如果 docker info 报错,通常是 Docker Engine 尚未启动,或当前用户没有使用 Docker 的权限。
1. 准备一个最小应用
新建一个目录,例如 hello-docker,在里面创建 app.py:
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write("你好,Docker!\\n".encode("utf-8"))
server = HTTPServer(("0.0.0.0", 8000), Handler)
print("Listening on port 8000")
server.serve_forever()
这里用 Python 自带的 HTTP 服务,不需要额外安装依赖。0.0.0.0 表示监听容器内所有网络接口;如果只监听 127.0.0.1,从容器外通常无法访问它。
2. 用 Dockerfile 描述运行环境
在同一目录创建名为 Dockerfile 的文件(注意大小写和文件名,不要加扩展名):
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]
逐行理解:
- FROM:选择基础镜像;这里包含精简版 Python 3.12 运行环境。
- WORKDIR:设置镜像内后续命令的工作目录;目录不存在时会创建。
- COPY:把构建上下文中的 app.py 复制进镜像的 /app。
- EXPOSE:声明应用预计使用的容器端口,主要是文档说明;它本身不会把端口发布到宿主机。
- CMD:容器启动时默认执行的命令。JSON 数组写法会直接运行程序,不经过 shell。
可以再加一个 .dockerignore,避免把无关文件送进构建上下文:
.git
__pycache__
*.pyc
.env
不要把密码、令牌等秘密写进镜像或提交到代码仓库。
3. 构建镜像
在 Dockerfile 所在目录执行:
docker build -t hello-docker:1.0 .
- docker build:根据 Dockerfile 构建镜像。
- -t hello-docker:1.0:给镜像命名并标记版本;格式常见为 名称:标签。
- 最后的 .:构建上下文,即 Docker 可以读取的当前目录。COPY 的源文件必须位于构建上下文内。
构建完成后检查:
docker image ls
4. 启动容器并访问应用
docker run –rm -d –name hello-docker -p 8080:8000 hello-docker:1.0
- –rm:容器停止后自动删除容器(不会删除镜像)。
- -d:后台运行。
- –name:给容器一个容易记住的名字。
- -p 8080:8000:把宿主机的 8080 端口映射到容器的 8000 端口。
现在访问 http://localhost:8080,或在终端执行:
curl <http://localhost:8080>
应看到 你好,Docker!。记住端口方向:-p 宿主机端口:容器端口。程序在容器里监听 8000,所以映射右侧是 8000;左侧 8080 是你在本机访问的入口。
5. 查看、停止和清理
docker ps # 查看正在运行的容器
docker ps -a # 查看所有容器,包括已停止的
docker logs hello-docker # 查看容器日志
docker stop hello-docker # 停止容器
docker image ls # 查看本机镜像
docker rmi hello-docker:1.0 # 删除镜像(确认没有容器正在使用)
这次运行用了 –rm,停止后容器会自动删除;没有使用 –rm 时,可用 docker rm <容器名或ID> 删除已停止的容器。删除容器和删除镜像是两件事。
6. 改代码后怎么更新?
镜像是在构建时制作的快照。改了 app.py 后,已有镜像和正在运行的容器不会自动更新。重新构建并启动即可:
docker build -t hello-docker:1.0 .
docker rm -f hello-docker # 如果旧容器还在运行,先删除它
docker run –rm -d –name hello-docker -p 8080:8000 hello-docker:1.0
开发时可以把本机文件挂载进容器,减少反复复制文件的步骤。例如在项目目录执行:
docker run –rm -d –name hello-dev -p 8080:8000 \\
-v "$PWD/app.py:/app/app.py:ro" hello-docker:1.0
这里 -v 本机路径:容器路径:ro 把本机文件只读挂载到容器中。注意:这个示例的 Python 程序不会自动热重载;改完代码后要重启容器才能让进程重新读取文件。生产环境要保存数据时,通常使用 named volume(命名卷);不要把容器可写层当作可靠的长期存储。容器删除后,未挂载出来的数据可能一并丢失。
7. 用 Docker Compose 管理启动配置
只有一个容器时,docker run 通常够用;项目有多个服务或启动参数较多时,可以用 Docker Compose 把配置写进 compose.yaml。在项目目录创建:
services:
web:
build: .
ports:
– "8080:8000"
启动、查看和停止:
docker compose up –build # 构建并启动
# 在浏览器访问 <http://localhost:8080>
docker compose ps # 查看服务状态
docker compose logs -f # 持续查看日志
docker compose down # 停止并移除 Compose 创建的容器和网络
Compose 负责根据配置协调容器;镜像仍由 Dockerfile 构建。后续可以在 services 下增加数据库等服务,并通过服务名互相连接,而不必手动记容器 IP。
8. 常见问题速查
- 浏览器连不上:确认容器在运行、端口映射正确,并且应用监听 0.0.0.0 而不是只监听 127.0.0.1。
- 改代码后结果没变化:容器运行的是旧镜像或旧进程;重新构建并重启,或正确设置开发挂载。
- 构建时报找不到文件:检查 docker build 最后的构建上下文路径,以及 COPY 的源文件是否在该目录内、是否被 .dockerignore 排除。
- 端口已被占用:换宿主机端口,例如 -p 8081:8000,然后访问 localhost:8081。
- 容器一启动就退出:看 docker ps -a 和 docker logs <容器名>;容器的主进程退出后,容器通常也会停止。
- EXPOSE 了还是不能从本机访问:EXPOSE 不等于端口映射,启动时仍需 -p,或在 Compose 中设置 ports。
9. 面向团队与生产环境:一套可落地的项目结构
先说明边界:Docker Compose 很适合本地开发、CI、测试环境,以及部署在单台主机上的小型生产服务;它本身不是多主机高可用调度平台。若需要跨主机调度、自动扩缩容、滚动发布和平台级故障恢复,应把镜像交给 Kubernetes 或云厂商容器平台等编排系统管理。Docker 官方也建议用基础 Compose 配置叠加生产环境配置,并在生产中移除源码挂载等开发设置。[1]
推荐的仓库布局
my-project/
├── compose.yaml # 全项目唯一的默认入口,编排完整应用
├── compose.production.yaml # 生产环境差异配置
├── .env.example # 环境变量名称与示例值,不放真实凭据
├── .dockerignore # 排除无关文件和敏感文件
├── .gitignore # 忽略本地配置与秘密文件
├── nginx/
│ └── default.conf # 反向代理、域名与 TLS 相关配置
├── backend/
│ ├── Dockerfile
│ ├── compose.dev.yaml # 可选:只在后端独立开发时使用
│ └── …
├── frontend/
│ ├── Dockerfile
│ ├── compose.dev.yaml # 可选:只在前端独立开发时使用
│ └── …
├── secrets/ # 本地开发用秘密文件;加入 .gitignore
└── ops/ # 可选:部署、备份、迁移等运维脚本
根目录的 compose.yaml 应是团队启动整套系统时的权威入口。你提到的 backend/docker-compose.yml、frontend/docker-compose.yml 可以保留,但我建议把它们定位为可选的独立开发配置,而不是另一套生产配置;否则端口、网络、环境变量容易出现两份真相。Compose 支持 compose.yaml 和 docker-compose.yml 等文件名,这里统一用较新的 compose.yaml 命名。
根目录 Compose:网关、前端、后端和数据库
下面是结构示例。端口、健康检查路径和应用环境变量需要按你的实际项目调整;生产环境的镜像应由 CI 构建、扫描并推送到可信镜像仓库。
services:
gateway:
image: nginx:stable-alpine # 示例;生产部署应锁定团队批准的版本或 digest
ports:
– "80:80"
# 对公网提供 HTTPS 时再配置 443 与证书/外部负载均衡
volumes:
– ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
– frontend
– backend
networks:
– edge
restart: unless-stopped
frontend:
build:
context: ./frontend
expose:
– "3000" # 仅供同一 Docker 网络中的服务访问,不发布到宿主机
networks:
– edge
restart: unless-stopped
backend:
build:
context: ./backend
expose:
– "8000"
environment:
DB_HOST: db
DB_NAME: app
DB_USER: app
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
– db_password
depends_on:
db:
condition: service_healthy
networks:
– edge
– data
restart: unless-stopped
db:
image: postgres:17 # 示例;生产部署应锁定经过验证的版本或 digest
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
– db_password
volumes:
– db-data:/var/lib/postgresql/data
networks:
– data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
edge:
data:
internal: true
volumes:
db-data:
secrets:
db_password:
file: ./secrets/db_password.txt
关键设计点:
- 只有 gateway 发布宿主机端口;前端和后端使用 expose 供 Docker 网络内部访问,数据库不发布端口。Compose 服务之间用服务名通信,例如 backend 连接 db:5432,不要写容器 IP。
- edge 让网关能够访问前端和后端;data 只连接后端与数据库,并设为内部网络,缩小数据库暴露面。实际网络边界仍要结合主机防火墙和云网络规则检查。
- depends_on 的普通写法只控制启动顺序,不代表应用已经可用。数据库配置了健康检查,因此后端可以等待 service_healthy;前后端也应按实际应用提供健康检查,并让网关/部署平台能够据此判断服务状态。
- 数据库数据放在命名卷 db-data,而不是容器可写层。命名卷不等于备份:还要制定备份、恢复演练、保留周期和数据库迁移方案。
- Compose secrets 会按服务授权,并以文件方式挂载到容器;上例适用于本地/单机演示。不要提交真实秘密文件。生产中优先接入云密钥管理或部署平台的 Secret 管理;本地文件型 secrets 也不能代替对宿主机和备份文件的保护。[2]
Nginx 反向代理示例
server {
listen 80;
server_name _;
location /api/ {
proxy_pass <http://backend:8000/>;
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 X-Forwarded-Proto $scheme;
}
location / {
proxy_pass <http://frontend:3000>;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
这里的 proxy_pass 写法会把 /api/ 前缀剥掉后转发给后端;如果后端路由本身包含 /api/,要相应调整配置。对公网服务还需配置 TLS(或在受信任的外部负载均衡处终止 TLS)、请求体大小/超时、访问日志与安全响应头;不要把示例里的 server_name _ 直接当作完整的域名和 TLS 配置。
开发与生产配置分开
开发时可以通过额外 Compose 文件加入源码挂载、调试端口和热重载;生产配置则不应把宿主机源码目录挂载进容器。用多文件叠加表达环境差异,避免复制整份 Compose 配置:[1]
# 本地开发:基础配置 + 开发覆盖
docker compose -f compose.yaml -f compose.dev.yaml up –build
# 生产配置先做合并结果检查,再部署
docker compose -f compose.yaml -f compose.production.yaml config
docker compose -f compose.yaml -f compose.production.yaml up -d
生产覆盖文件可以放生产端口、重启策略、日志驱动和环境专属设置;镜像应使用 CI 发布的明确版本(更严格时锁定 digest),不要依赖 latest。正式上线还应把配置和秘密从代码仓库分离,并明确谁负责 TLS、数据库托管、备份和回滚。Compose 的配置可移植性有限,生产部署前要在目标运行环境验证合并后的配置。
Dockerfile 与镜像的团队基线
- 使用多阶段构建:构建阶段装编译工具和开发依赖,运行阶段只复制必要产物,减少最终镜像内容。
- 选择可信且尽量精简的基础镜像,固定并定期更新版本;生产发布应记录镜像版本/digest,并在 CI 中做漏洞扫描与构建测试。
- 应用不需要 root 权限时,在 Dockerfile 中创建并使用非 root 用户;进程以前台主进程运行,日志写到 stdout/stderr,便于容器平台收集。
- .dockerignore 排除 .git、缓存、本地构建产物、.env、秘密文件等,避免它们进入构建上下文或镜像。
- 不要把密码、令牌写入 Dockerfile 的 ENV/ARG、镜像层或 Git;构建私有依赖时使用 BuildKit build secrets,而不是把凭据复制到镜像里。多阶段构建、精简可信基础镜像和非 root 用户都是 Docker 官方列出的构建最佳实践。[3]
日常验证与上线检查
# 检查 Compose 文件能否解析,并查看最终配置
docker compose config -q
docker compose config
# 本地启动、观察状态和日志
docker compose up –build -d
docker compose ps
docker compose logs -f gateway backend
# 停止并移除容器/网络;命名卷默认保留
docker compose down
上线前至少确认:
这套结构的核心不是“目录越多越企业级”,而是让团队有一个明确入口、服务间边界清楚、开发与生产差异可审查、秘密和数据有明确生命周期。对于单机部署,Compose 可以承担编排;当可靠性、规模或治理要求超出单机能力时,应迁移到专门的容器平台,而不是继续堆叠 Compose 文件。
最后复述一遍整条链路
写应用代码
→ Dockerfile 描述环境与启动方式
→ docker build 生成镜像
→ docker run(或 docker compose up)启动容器
→ 用端口映射访问服务
→ 用日志、挂载和 Compose 管理运行过程
建议先不背命令,遇到问题时先问:我现在处理的是代码、镜像、容器、端口,还是数据挂载? 分清对象后,大多数 Docker 基础问题会容易定位。
官方文档
- Docker Get Started
- Dockerfile 概念与参考
- 运行容器(docker run)
- Docker Compose

