01.Docker 指南
NOTE
此文档为较早版本,有些地方可能不是非常明确,待修改。
Docker 核心三件组成
Dockerfile
Dockerfile 用于构建image,是构建image的蓝图。
Dockerfile 指令
Dockerfile 的指令按照在构建镜像和容器运行,分为两类。
即,有些指令只在构建镜像时才会被执行,在容器运行时不会执行;反之亦然。
构建镜像时执行
FROM
作用:
指定基础镜像,是所有 Dockerfile 必须的第一条指令。后续的指令都将在此基础上执行。
示例:
FROM python:3.13-slim这表示使用官方的 Python 3.13 轻量级镜像作为基础。
RUN
作用:
执行指令,并将结果保存到镜像中。常用于安装软件包、配置环境等。
每个 RUN 指令执行时会创建一个新的镜像层(Layer),并在该层上执行指令。新层不是上一层的完整副本,而是基于上层的增量更改,并记录本次 RUN 操作带来的变化。这种设计可以避免镜像不断变大,但不断创建新层仍会带来一定的额外空间开销。
执行 RUN 后的文件系统更改会被永久保存在镜像中。在容器运行时,Docker 会把这些层组合成一个联合文件系统(例如 OverlayFS),展现为一个完整的文件系统,供容器使用。从镜像结构来看,层依然是分开的,不会被“解散”。
示例:
RUN apt-get update && apt-get install -y vim这条指令会在镜像中安装 Vim 编辑器。
COPY
区分:ADD。
**作用:**将文件或目录从构建上下文(本地文件系统)复制到镜像中的指定位置。
示例:
COPY . /app将当前目录(宿主机目录)下的所有文件复制到镜像的
/app目录。
ADD
区分:COPY。
**作用:**类似于 COPY,但具有额外功能,如自动解压归档文件或从 URL 下载文件。
示例:
ADD https://example.com/file.tar.gz /app/这条指令会下载指定的 tar.gz 文件并解压到镜像的
/app/目录。相较于
COPY,COPY仅复制本地文件和目录,行为更可预测,推荐优先使用COPY,除非需要ADD的额外功能。
WORKDIR
作用:
设置后续指令的工作目录。如果目录不存在,Docker 会自动创建。
示例:
WORKDIR /app设置
/app为当前工作目录。
LABEL
(使用案例待补充)
作用:为镜像添加元数据(如作者信息、版本等),通常用于描述镜像。
示例:
LABEL maintainer="yourname@example.com"设置镜像的维护者信息。
ARG
对比ENV。
**作用:**定义构建时的变量。
示例:
ARG APP_VERSION=1.0
RUN echo "Version: $APP_VERSION"在构建时可以通过 --build-arg 参数覆盖 APP_VERSION。
容器运行时执行
CMD
区分:RUN和ENTRYPOINT。
作用:指定容器启动时的默认命令。如果 docker run 时指定了其他命令,则 CMD 会被覆盖。
示例:
CMD ["python", "app.py"]默认在容器启动时运行
python app.py。
NOTE
CMD 指令有两种主要的格式:
Exec 格式(推荐):
CMD ["executable", "param1", "param2"]docker 会将每一个元素看作一个整体执行,如使用CMD ["flask run"]无法达到预期效果,因为docker会将"flask run"看作一个可执行文件。可能会报以下错误:
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "flask run": executable file not found in $PATH: unknown.正确的使用方法应该是:
CMD ["flask", "run"]或者后文的 Shell 格式:
CMD flask runShell 格式:
CMD executable param1 param2这种格式会通过 /bin/sh -c 来执行命令,因此可以使用 shell 特性。
ENTRYPOINT
作用:为容器设置一个固定的入口点,指定不可更改的命令,容器启动时总是执行该命令,运行容器时指定参数无法覆盖该指令。可以与 CMD 结合使用,可以传递默认参数。
示例:
ENTRYPOINT ["python"]
CMD ["app.py"]容器启动时将执行
python app.py。ENTRYPOINT设置了python作为固定命令,CMD设置了默认参数app.py。
对比CMD ,CMD 设置的是默认参数,容易被 docker run 时的命令覆盖。
EXPOSE
作用:声明容器将要监听的网络端口。它是一个文档化的功能,不会实际映射端口。
示例:
EXPOSE 5000声明容器内部的 5000 端口用于监听。
VOLUME
作用:创建一个挂载点,允许在容器和宿主机之间共享数据。用于持久化数据或在多个容器之间共享数据。
示例:
VOLUME ["/data"]声明 /data 为一个挂载点。容器内部的/data目录将会和docker指定一个默认的宿主机上的目录共享,当下一次容器启动时,仍可加载/data目录中的文件(除非在宿主机上删除),当容器被删除时,宿主机上的目录及其中的文件会保留,不会因为容器删除而自动清除,可以手动在宿主机上删除该目录或其中的文件。
USER
作用:指定运行容器时的用户,默认是 root。
示例:
USER appuser指定以 appuser 用户身份运行容器。
镜像构建和容器运行时都执行
ENV
对比ARG。
设置环境变量,这些变量在构建时和容器运行时都可用。
示例:
# 使用官方的 Python 3.13 轻量级镜像作为基础镜像
FROM python:3.13-slim
# 设置环境变量
ENV APP_HOME=/app
# 使用环境变量设置工作目录
WORKDIR $APP_HOME
# 复制当前目录内容到容器中的工作目录
COPY . $APP_HOME
# 设置另一个环境变量
ENV PYTHONUNBUFFERED=1
# 创建虚拟环境并安装依赖
RUN python3 -m venv venv && \
./venv/bin/pip install --no-cache-dir -r requirements.txt
# 暴露5000端口
EXPOSE 5000
# 设置容器启动时执行的命令
CMD ["./venv/bin/python", "app.py"]
ENV APP_HOME=/app设置了一个名为APP_HOME的环境变量。
WORKDIR $APP_HOME使用了前面设置的APP_HOME环境变量,设置工作目录为/app。
ENV PYTHONUNBUFFERED=1设置了PYTHONUNBUFFERED环境变量,以确保 Python 输出被实时刷新。
在构建镜像时这些环境变量可以被dockerfile后续的指令识别,在使用镜像运行容器时,容器内的应用程序,如flask,也可以获得该环境变量,如通过 os.environ.get('APP_HOME') 获取 APP_HOME 的值。
image
image是创建container的镜像,container从image中创建。
container
container是容器,容器内部执行相关操作。
docker 高级功能
运行指定版本的容器
使用 docker run ubuntu 默认运行一个版本为 latest 的 ubuntu 镜像的容器,如果想要指定 ubuntu 镜像的版本:
- 去 docker hub 上查看你需要镜像的版本号
- 在
docker run ubuntu基础上显式指定版本号,使用冒号开始:如docker run ubuntu:17.10。
在后台运行 docker 容器
docker 默认使用前台运行的方式运行容器,如使用 docker run ubuntu sleep 200 将在前台显式的执行 sleep 200 的指令,在此期间控制台将“无法返回”,直到 200 秒结束。(您仍然可以通过新建一个控制台连接到 docker,并进一步停止该容器的执行)
使用 -d 参数指定以后台运行容器: docker run -d ubuntu sleep 200
使用 -d 使容器在后台执行后,使用 attach 重新链接到该容器,使其回到前台:
- 使用
docker ps查看当前运行的容器的 hash - 使用
docker attach <hash>链接到该容器
查看容器的详细信息
使用 docker inspect <hash> 查看容器的更多详细信息。
链接端口号
将 docker 容器的端口号和宿主机端口号建立映射,使用 -p 参数: docker run -p 5000:8080 jenkins 。
其中,第一个 5000 是宿主机的端口号,第二个 8080 是 docker 容器的端口号。
建立端口映射后,宿主机流入
5000的流量都将被路由到 docker8080端口,从而能够访问到容器内部。
建立映射卷
使容器内部数据持久化。
默认情况下,当容器停止时,容器内部所生成的所有数据都将被销毁,下次启动该容器时,容器内部的文件将不存在。为了持久化产生的数据,使得容器下一次重新运行时还能读取这些文件,需要和宿主机建立文件映射,将容器产生的文件实际上保存到宿主机上而不是容器内。
使用 -v 参数建立容器和宿主机上的文件映射: docker run -p 5000:8080 -v your/root/host/path:docker/container/path jenkins ,这样,容器内产生的文件在容器内部显示保存到了 docker/container/path ,但实际上是保存到了宿主机上的 your/root/host/path 目录中。
下一次再次运行该容器时,如果仍运行 docker run -p 5000:8080 -v your/root/host/path:docker/container/path jenkins ,那么数据将会显示为保存的效果,因为容器会从 docker/container/path 下寻找文件,且可以找到实际上存储在宿主机上 your/root/host/path 的文件。
案例
在 docker 中运行 flask 应用
步骤
创建虚拟环境,建立flask应用
详细的环境配置和代码在后文,首先关注 docker 和 flask 应用。
目录树如下(未列出虚拟环境目录):
cmdDOCKER-FLASK |----------- |-- .dockerignore |-- .flaskenv |-- app.py |-- Dockerfile |-- requirements.txt |-- templates |-- |-- login.html- 在
.flaskenv中指定FLASK_RUN_HOST=0.0.0.0和FLASK_RUN_PORT=5000。
- 在
编写dockerfile文件
Dockerfile:
dockerfileFROM python:3.13-slim WORKDIR /app COPY . /app RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple EXPOSE 5000 CMD ["flask", "run"].dockerignore:
dockerfile.docker-flask_venv/ # 虚拟环境目录 __pycache__/构建image:
在cmd中运行:
cmddocker build -t docker-flask-app .启动容器
在cmd中运行:
dockerfiledocker run -p 8888:5000 docker-flask-app在宿主机浏览器中访问
http://localhost:8888/效果如下:

环境配置和代码
环境配置
python版本:
采用python3.12版本,使用conda创建虚拟环境。
代码
app.py:
from flask import Flask, render_template, request, redirect, url_for, session
import os
app = Flask(__name__)
app.secret_key = os.urandom(24)
# Sample user data
dummy_user = {"username": "user", "password": "password"}
@app.route("/")
def home():
if "username" in session:
return f"<h1>Welcome {session['username']}! You are logged in.</h1><br><a href='/logout'>Logout</a>"
return redirect(url_for("login"))
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
if username == dummy_user["username"] and password == dummy_user["password"]:
session["username"] = username
return redirect(url_for("home"))
else:
return "<h1>Invalid credentials. Please try again.</h1>"
return render_template("login.html")
@app.route("/logout")
def logout():
session.pop("username", None)
return redirect(url_for("login"))requirements.txt:
blinker==1.8.2
click==8.1.7
colorama==0.4.6
Flask==3.0.3
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==3.0.2
python-dotenv==1.0.1
Werkzeug==3.1.2.flaskenv:
FLASK_APP=app.py
FLASK_ENV=development
FLASK_RUN_HOST=0.0.0.0
FLASK_RUN_PORT=5000login.html:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<h2>Login Page</h2>
<form method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br><br>
<input type="submit" value="Login">
</form>
</body>
</html>指令
删除镜像
强制删除
podman rmi --force <image_hash>Docker 指令附录
记录 Docker 基本指令。
Dockerfile 相关
根据 Dockerfile 构建镜像
设当前目录下有一个 Dockerfile:
docker build .-t 参数用于指定构建镜像的名称和标签:
docker build . -t "custom-name-image:latest"这样,使用 docker images 检查的 REPOSITORY 和 TAG 分别是 custom-name-image 和 latest ,如果不指定标签,如 docker build . -t "custom-name-image" ,则默认 TAG 为 latest 。
如果不使用 -t 参数,则构建的镜像名称为一个 ID 值(哈希值),之后需要通过这个 ID 值操作镜像。
容器
启动容器
docker run demo-image这将 demo-image 的镜像运行为一个容器。
-p 参数
-p 参数指定映射容器和宿主机的端口,格式:<宿主机端口>:<容器端口> (注意参数的位置,在 run 之后,镜像名称之前)
docker run -p 5000:80 demo-image这将将宿主机上流入 5000 端口的流量全部路由到容器内的 80 端口,从而访问容器内的 80 端口的内容。
--name 参数
指定容器运行时的名称:
docker run --name=redis1 redis表示运行 redis 镜像为容器,其中容器的名称为 redis1
-d 参数
指定后台运行容器:
docker run -d redis1此时运行名称为 redis1 的容器,将在后台执行。
启动一个停止的容器
设一个停止的容器名称为 redis1 :
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2ceb686a9a83 redis "docker-entrypoint.s…" 11 minutes ago Exited (0) 11 minutes ago redis1启动这个容器:
docker start redis1同样可以指定后台运行,使用 -d 参数:
docker start -d redis1删除容器
删除所有容器(无论是否正在运行):
docker rm -afrm:删除容器-a (--all):删除所有容器(包括正在运行和已停止的)-f (--force):强制删除,即使容器正在运行
容器间交互
使用 –-link 参数使得一个容器可以通过容器的名称访问另一个容器:
docker run --link redis:redis voting-app其中,第一个 redis 表示之前已经启动好的一个容器的名称,第二个 redis 表示在这个新启动的容器中,要访问之前的 reids 容器,使用的别名,这里,新的容器内部可以通过使用 redis 来访问之前启动好的容器 redis 。如果修改为 –link redis:redis1 ,则现在可以通过 redis1 访问之前启动好的容器 redis 。
限制 CPU 和内存使用
默认情况下,分配给容器的宿主机 CPU 和内存没有限制,可以在启动容器时显式指定最大允许的 CPU 或内存,使用 --cpus 和 --memory 选项:
docker run --cpus=.5 ubuntu表示最大允许使用宿主机 CPU 的 50% 。
docker run --memory=100m ubuntu表示最大允许使用宿主机内存的 100MB 。