10 分钟掌握 Docker

“在我电脑上明明能跑”以前是个笑话。Docker 却把它变成了一种交付办法:把应用和运行环境一起打包。这一页先讲清镜像与容器,再解释其他概念,也会处理每个初学者都会撞上的三个坑:端口被占用、数据突然消失,以及不知不觉吃掉的 40GB 磁盘空间。

🎙️ 发布并录制于: · 更新于 ·

01问题:“在我电脑上明明能跑”

应用要正常运行,光有代码还不够。它还需要特定版本的 Python、系统库、配置和环境变量。过去部署应用,就是在另一台机器上重新搭好这一整套东西,然后祈祷别出错。容器会把应用和完整的运行环境装进一个封闭、便携的箱子,到哪里都用同一种方式运行。它和货运集装箱是一个道理:每到一个港口,都不必把你的家具拆开重装。

# the entire pitch, in one command — run redis without installing redis:
docker run redis

# no apt install, no config files, no version conflicts with
# the redis your OTHER project needs. sealed box.
你遇到的第一个报错
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Docker 既有客户端,也有一个在后台运行的服务,也就是守护进程。这个报错说明后台服务没启动。在 Mac 或 Windows 上,通常只是 Docker Desktop 没打开。启动它,等鲸鱼图标稳定下来,再试一次。它没坏,只是还没醒。

02镜像与容器:最重要的区别

只要弄懂这一点,Docker 就不会再显得混乱。镜像是一份冻结的模板:只读、可以分享,也有版本。容器则是镜像的一个运行实例。前者像食谱,后者像照着食谱做出的蛋糕;也像类和对象。一份镜像可以启动二十个容器,每个容器都有自己的状态。删除容器,不会伤到镜像。

docker pull python:3.12      # download an image (the template)
docker images                # list templates on this machine

docker run -it python:3.12   # bake a cake: image → running container
docker ps                    # list RUNNING containers
docker ps -a                 # ...including stopped ones (they linger!)
镜像名称怎么读
python:3.12 的格式是名称、冒号、标签,标签就是版本。省略标签,相当于使用 :latest。这个名字全靠发布者自觉:“latest”只是发布者最后推送的版本,两次拉取之间可能悄悄发生变化。正经项目一定要固定标签,用 python:3.12,不要只写 python

03docker run:真正要记的参数

docker run 的常见用法,九成都靠四个参数。-d 表示在后台运行;--name 给容器起名,后面才能方便地引用它;-p 把端口穿过容器这堵墙;-e 传入环境变量。端口映射值得单独画张图,因为几乎每个人第一次都会看反。

docker run -d --name web -p 8080:80 -e MODE=prod nginx

-p 8080:80
   └──┬─┘└┬┘
   YOUR machine's port : the CONTAINER's port
   "requests to localhost:8080 → into the box at :80"
   mnemonic: host first, box second — outside before inside
这个报错你一定会遇到
Error ... failed to bind host port 0.0.0.0:8080: address already in use  (或者 port is already allocated
你的电脑上已经有程序占用了 8080 端口。最常见的情况,是同一个容器的旧实例还在运行。用 docker ps 找到它,再用 docker stop 停掉;也可以换一个宿主机端口,例如 -p 8081:80。容器内部的端口不会互相冲突,真正会冲突的只有宿主机这一侧。

04查看运行中的容器

排查问题基本只用三组命令。logs 看容器输出了什么;exec 在容器里面打开一个命令行;stoprm 负责停止和删除容器。容器“跑不起来”时,百分之九十五的答案都在日志里。容器的主进程一退出,容器也会退出,所以启动时崩溃,看起来就像“它自己停了”。

docker logs web              # everything it printed
docker logs -f web           # follow live (like tail -f)

docker exec -it web sh       # a shell INSIDE the container
  # look around: ls, cat config, ps — then exit

docker stop web              # polite stop
docker rm web                # remove the (stopped) container
“怎么刚启动就退出了”
你执行了 docker run,但 docker ps 里什么都没有。容器并没消失,它只是立刻崩溃或执行完了。docker ps -a 会显示它和退出码,docker logs <name> 会告诉你原因。容器的寿命和主进程完全一样长。它不是一台在后台空转的虚拟机,而是装在盒子里的一个进程。

05Dockerfile:把自己的镜像写成配方

Dockerfile 是构建镜像的配方:先选一个基础镜像,再复制文件、执行安装命令,最后说明容器启动时要运行什么。你实际会读写的 Dockerfile,大多离不开下面这五种指令。

FROM python:3.12-slim          # start from this base image
WORKDIR /app                   # cd (and mkdir) inside the image
COPY requirements.txt .        # copy deps list first — see next section!
RUN pip install -r requirements.txt
COPY . .                       # now the rest of the code
CMD ["python", "app.py"]       # what runs when a container starts
docker build -t myapp:1.0 .    # bake it (-t = name:tag, . = here)
docker run -p 8000:8000 myapp:1.0

06分层与缓存:COPY 顺序为什么重要

Dockerfile 的每一行都会产生一个,Docker 会缓存每一层。只要某一行和它之前的内容都没变,这一层就能立刻复用。由此只需要记住一条规则,也是优化 Dockerfile 最有效的一条:越不常变的内容越靠前,越常变的内容越靠后。所以要先复制依赖清单,再复制代码。

# ✗ naive order — change one line of code,
#   and pip reinstalls EVERYTHING (5 min per build):
COPY . .
RUN pip install -r requirements.txt

# ✓ cache-friendly — deps layer only rebuilds when
#   requirements.txt itself changes (build: ~2 sec):
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

这也解释了两个现象:使用同一个基础镜像的多个镜像会共享磁盘空间;第一次构建很慢,第二次却几乎瞬间完成。缓存并不神秘,它就是一叠冻结的差异层。从第一行发生变化的地方开始,后面的缓存会依次失效。

07数据卷:你的数据究竟去了哪里

这个坑真的会让人丢数据:容器被删除时,它自己的文件系统也会消失。你在容器里运行数据库,积累了一个月的数据,然后执行 docker rm,数据就全没了。数据卷是一块放在容器外部的持久化存储,再挂载到容器里面。数据库一定要使用数据卷。

# named volume — Docker manages where it lives
docker run -d --name db \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

# bind mount — YOUR folder appears inside the box
# (this is how dev setups live-reload code)
docker run -v ./src:/app/src myapp

docker volume ls             # see your volumes
说一遍,就别再忘
容器是牲口,数据卷是牲口棚。你应该能随时删除并重建任何容器,而且不丢任何数据。做不到,就说明有些状态被错误地留在了容器内部。测试方法很直接:对整套服务执行 docker rm,重新启动后,看看数据还在不在。务必在生产环境替你做这次测试之前,自己先测一遍。

08Compose:用一个文件管理整套服务

真正的应用通常有好几个容器,比如应用、数据库和缓存。docker compose 会读取一个 YAML 配置文件,其中描述了所有容器,然后把它们当成一个整体管理。还有一个很方便的地方:同一个 Compose 文件里的容器,可以直接用服务名找到彼此。应用连接的是 db:5432,不用写 IP 地址。

# compose.yaml
services:
  web:
    build: .
    ports: ["8000:8000"]
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/app
  db:                          # ← "db" is also its hostname
    image: postgres:16
    volumes: ["pgdata:/var/lib/postgresql/data"]
volumes:
  pgdata:
docker compose up -d         # start everything
docker compose logs -f web   # follow one service's logs
docker compose down          # stop and remove (volumes survive)

09清理:不知不觉占掉的 40GB

Docker 从不会主动删除东西。拉取过的每个镜像、停止的每个容器、构建留下的每个孤立层,都会悄无声息地一直留在磁盘上。半年以后,你问“磁盘怎么满了”,答案往往只有一个词:Docker。下面两个命令就能管理这些空间。

docker system df             # how much space is Docker eating?

docker system prune          # delete stopped containers + dangling images
docker system prune -a       # ...plus ALL unused images (aggressive)

# prune never touches: running containers, their images, volumes.
# volumes need explicit: docker volume prune (careful — data!)

10速查表

按照你脑子里正在问的那句话来查。

# "run this thing"
docker run -d --name x -p 8080:80 image:tag

# "what's running / what just died?"
docker ps · docker ps -a

# "why is it broken?"
docker logs -f x

# "let me look inside"
docker exec -it x sh

# "port is already allocated"
docker ps → stop the old one, or -p 8081:80

# "my build is slow"
copy deps file → install → THEN copy code

# "where did my data go" (asked once, never again)
volumes for anything that must survive: -v name:/path

# "disk is full"
docker system df → docker system prune

# "start/stop the whole stack"
docker compose up -d · docker compose down

掌握这些,就足以在工作中用好 Docker。命名空间、控制组和镜像仓库还能继续深挖,但日常每周真正会用到的,就是这一页的内容。Docker 也正好能串起整套开发工具:容器本质上是 Linux 进程,所以命令行知识在容器里照样适用;而由 Git 跟踪的 Dockerfile,已经记录了完整的部署方式。

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.