Docker 入门指南:从「我电脑上能跑」到「处处能跑」

一、为什么你的代码「在我电脑上能跑」?

你一定遇到过这样的场景:项目在本地运行得好好的,部署到服务器就各种报错——Python 版本不对、缺少系统依赖、环境变量没设……排查半天,最后发现是一个 .so 文件的版本差异。

这个问题的本质是:环境不一致

传统虚拟机能解决一部分问题,但一台虚拟机动辄几个 GB 的磁盘占用、分钟级的启动速度、大量的内存开销,让它在轻量开发场景下显得笨重。

Docker 用一个更巧妙的思路解决了这个问题。

Docker 入门指南


二、Docker 是什么?

一句话概括:Docker 是一个让你把应用及其所有依赖打包成一个「集装箱」,在任何装了 Docker 的系统上都能一模一样运行起来的平台。

这个「集装箱」就是容器(Container)

2.1 容器 vs 虚拟机:本质区别

维度 容器(Docker) 虚拟机
虚拟化层级 操作系统级(共享宿主机内核) 硬件级(每台 VM 独立 OS)
启动速度 毫秒~秒级 分钟级
磁盘占用 ~50-300 MB 几个 GB
内存开销 极低(MB 级) 较高(GB 级)
单机密度 100-500+ 10-20 台

简单说:虚拟机是装了一整套操作系统,容器只是宿主机上的一个隔离进程。它利用 Linux 的 namespace 和 cgroups 技术实现资源隔离,但和主机共享同一个内核。这让容器既轻量又高效。

据 Stack Overflow 2025 年开发者调查,超过 64% 的专业开发者日常使用容器技术。


三、核心概念速览

在动手之前,先理解四个关键概念:

3.1 镜像(Image)

镜像是容器的「蓝图」——一个只读模板,包含运行应用所需的一切:代码、运行时、系统库、配置。镜像采用分层存储,每一层对应构建过程中的一条指令,层与层之间共享缓存,大幅提升构建效率。

3.2 容器(Container)

容器是镜像的运行实例。同一个镜像可以启动多个互相隔离的容器,每个容器有自己的文件系统、网络栈和进程树,但共享宿主机内核。

3.3 Dockerfile

Dockerfile 是镜像的「配方」——一个文本文件,逐条描述如何构建镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// FROM 指定基础镜像
FROM python:3.12-slim

// WORKDIR 设置工作目录
WORKDIR /app

// COPY 将文件复制到镜像中
COPY requirements.txt .

// RUN 执行命令安装依赖
RUN pip install --no-cache-dir -r requirements.txt

// 继续复制应用代码
// 第一个 . 代表当前目录
// 第二个 . 代表镜像中的当前目录(WORKDIR
COPY . .

// CMD 定义容器启动命令
CMD ["python3", "app.py"]

3.4 Docker Compose

当你需要同时运行多个容器(比如 Web 应用 + 数据库 + 缓存),一个个敲 docker run 就太痛苦了。Compose 让你用一个 YAML 文件定义所有服务,一条命令全部启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: redis:7-alpine
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: mysecret
volumes:
- pgdata:/var/lib/postgresql/data

volumes:
pgdata:

3.5 Docker 网络

Docker 网络本质是在宿主机上构建的一层虚拟网络抽象,用于解决容器之间、容器与宿主机之间、以及容器与外部网络之间的通信问题。核心由 Linux 网络命名空间(Network Namespace)+ 虚拟网卡 + 虚拟交换设备(bridge)+ iptables/NAT 组成。

Docker 默认网络模式为 bridge,所有的容器都默认连接到这个网络,每个容器都分配了一个内部的 IP 地址,在这个内部子网里面,容器可以通过内部 IP 地址互相访问,但容器网络与宿主机网络是隔离的。
同一个子网的容器可以互相通信,而跨子网则不可以通信。

另一种常用模式是 host,Docker 容器直接共享宿主机网络,容器直接使用宿主机的 IP 地址,而且无需 -p 参数进行端口映射,容器内的服务直接运行在宿主机的端口上,通过宿主机的 IP 和端口就能访问到容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建一个自定义网络
docker network create my-network

# 运行容器并加入到自定义网络
docker run -d --name my-app --network my-network nginx

# 同一个子网中容器通信:不需要 IP ,直接用名字
ping my-app

# 删除子网
docker network rm my-network

# 使用 host 模式运行容器
docker run -d --name my-app --network host nginx

四、日常必备命令

以下是你日常开发中最常用的命令,掌握了它们就覆盖了 80% 的场景。

4.1 镜像管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 拉取镜像
docker pull nginx:alpine

# 列出本地镜像
docker images

# 通过 Dockerfile 构建镜像(-t 打标签)
# 最后的 . 代表当前目录(Dockerfile 所在位置)
docker build -t my-app:v1 .

# 删除镜像
docker rmi my-app:v1

# 清理未使用的镜像
docker image prune

4.2 容器生命周期

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
# 后台运行容器,将宿主机的 8080 端口映射到到容器的 80 端口,命名为 my-nginx
docker run -d -p 8080:80 --name my-nginx nginx:alpine

# 运行容器,并挂载数据卷(宿主机的 /host/data 映射到容器的 /container/data)
docker run -v /host/data:/container/data --name my-app my-app:v1

# 运行容器,并设置环境变量
docker run -e "ENV=production" --name my-app my-app:v1

# 查看运行中的容器
docker ps

# 查看所有容器(包括已停止的)
docker ps -a

# 优雅停止
docker stop my-nginx

# 重新启动已停止的容器
docker start my-nginx

# 在容器停止后自动删除它
docker run --rm --name temp-container nginx:alpine

# 设置容器重启策略(如 always、unless-stopped)
docker run -d --restart always nginx:alpine

# 删除容器
docker rm my-nginx

# 实时查看日志
docker logs -f my-nginx

# 进入容器内部
docker exec -it my-nginx /bin/sh

# 创建一个容器但不启动它
docker create --name my-nginx nginx:alpine

4.3 数据卷管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建命名卷
docker volume create my-data

# 查看卷详情
docker volume inspect my-data

# 列出数据卷
docker volume ls

# 删除数据卷
docker volume rm my-data

# 删除未使用的数据卷
docker volume prune

4.4 Docker Compose 常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 启动所有服务(后台运行)
docker compose up -d

# 查看运行状态
docker compose ps

# 查看日志
docker compose logs -f

# 停止并清理
docker compose down

# 停止并清理包括数据卷
docker compose down -v

# 只停止不删除
docker compose stop

# 重新启动
docker compose start

4.5 清理空间

1
2
# 一键清理所有未使用的资源(谨慎使用)
docker system prune -a

五、动手实战:从零部署一个 Web 应用

假设你要用 Python FastAPI + Redis 搭建一个简单的计数器应用。来看看用 Docker 怎么做。

5.1 第一步:编写应用代码

app.py

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

app = FastAPI()
r = redis.Redis(host="redis", port=6379, decode_responses=True)


@app.get("/")
def index():
count = r.incr("visits")
return {"message": "Hello Docker!", "visits": count}

5.2 第二步:编写 Dockerfile

1
2
3
4
5
6
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5000"]

5.3 第三步:编写 Compose 文件

compose.yaml:

1
2
3
4
5
6
7
8
9
10
11
services:
web:
build: .
ports:
- "5000:5000"
depends_on:
- redis

redis:
image: redis:7-alpine
restart: unless-stopped

5.4 第四步:启动!

1
docker compose up -d

访问 http://localhost:5000,每次刷新页面,计数器都会递增。整个环境——Python 运行时、FastAPI 依赖、Redis——全部封装在容器中,换一台机器也是一条命令启动。


六、最佳实践清单

这是我个人总结的几个最影响实际体验的实践:

  1. 使用轻量基础镜像:优先选 alpineslim 版本,别用 latest。镜像大小可以从 800MB 降到 30MB。
  2. 多阶段构建:把编译和运行分开,最终镜像只包含运行时需要的文件。
  3. 利用 .dockerignore:别把 node_modules.gitvenv 之类的无关文件打到镜像里。
  4. 固定镜像版本:用 python:3.12-slim 而不是 python:latest,避免上游更新导致意外行为。
  5. 以非 root 用户运行:在 Dockerfile 里加 USER 指令,提升安全性。
  6. 数据持久化用命名卷:开发时用 bind mount 方便热更新,生产环境用命名卷保证数据安全。

七、总结

  • Docker 解决的是环境一致性问题——从开发到生产,同一个镜像,同样的行为。
  • 核心工作流只有三步:写 Dockerfile → 构建镜像 → 运行容器。Compose 帮你管多容器应用。
  • 投入一个下午就够了——装好 Docker Desktop,试着把你手头的项目容器化跑起来,你会发现「它在我电脑上能跑」再也不会成为问题。