文章目录
- 1.背景
- 2.Docker镜像的简单理解
- 3.制作镜像
-
- 3.1.创建文件
- 3.2.执行构建
-
- [3.2.1 解决报错](#3.2.1 解决报错)
- 3.2.2.检查构建结果
- 3.3.测试运行
- 4.部署
- 5.高级操作
-
- 5.1.打包conda环境
- 5.2.查看容器列表
- 5.3.查看容器内的文件
- [5.4. docker run / docker start的区别](#5.4. docker run / docker start的区别)
1.背景
目前用Python做了个服务器程序,后续可能需要将其整个发给客户,让其跑在客户的服务器上。了解了一下,用Docker的方式发布给客户,可能是最简单的方式了。
那就了解并制作一下自己程序的Docker镜像吧。
2.Docker镜像的简单理解
这些理解仅代表个人观点,不一定是对的。
- Docker镜像是类似一个系统镜像的文件(比如Windows10.iso),静态、不允许修改的。
镜像无法被直接启动,其必须被实例化为容器才可以被启动。从一个镜像可以实例化出多个容器。
当镜像被制作出来后,就没办法直接对其进行修改,假如要更改,就必须要使用原始的Dockerfile进行"编译"(正如exe需要拿源码编译一样)
一个简单的类比是
| Docker | 虚拟机(VMware) |
|---|---|
| Docker镜像 | 系统镜像(比如Windows10.iso) |
| 容器 | 安装到虚拟机中的某个系统(比如下图中的Windows7、Windows10 x64等) |

3.制作镜像
我是在Ubuntu 22.04下操作的。下面以一个简单的Flask程序镜像为例。
3.1.创建文件
项目结构
新建一个flask-docker文件夹,然后分别创建这三个文件
bash
flask-docker/
├── Dockerfile
├── requirements.txt
└── app.py
如下图所示

其中各个文件的内容为:
python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Flask in Docker!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
requirements.txt:
bash
flask==3.0.0
Dockerfile:
bash
# 使用 Python 3.11 的 Alpine 基础镜像(最小化)
FROM python:3.11-alpine3.18
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装依赖(使用 --no-cache-dir 减少镜像大小)
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY app.py .
# 暴露端口
EXPOSE 5000
# 设置环境变量(生产环境建议设置)
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
# 启动命令
CMD ["flask", "run", "--port=5000"]
3.2.执行构建
然后在此工程目录下执行
bash
# 构建镜像
docker build -t flask-minimal:latest .
注意不要漏了最后那个.,它是表示使用当前目录下的Dockerfile来进行构建

3.2.1 解决报错
假如执行时报错,类似这样
shell
ERROR: failed to solve: python:3.11-alpine3.18: failed to resolve source metadata for docker.io/library/python:3.11-alpine3.18: failed to do request: Head "https://docker.m.daocloud.io/v2/library/python/manifests/3.11-alpine3.18?ns=docker.io": dial tcp: lookup docker.m.daocloud.io on 127.0.0.53:53: server misbehaving

那是因为官方的源好像屏蔽了中国用户或者别的原因,会无法下载镜像。因此需要直接在系统中设置一下/etc/docker/daemon.json。找到并修改这个文件(假如文件不存在,就创建此文件),修改其信息为
bash
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com"
]
}
然后
bash
# 重启 Docker
sudo systemctl daemon-reload
sudo systemctl restart docker
或者,直接在源镜像前面加上docker.1ms.run/,这样就可以通过docker.1ms.run服务器来下载了。
bash
# 使用 Python 3.11 的 Alpine 基础镜像(最小化)
FROM docker.1ms.run/python:3.11-alpine3.18 # 注意看这里和前面的区别
.
.
.
3.2.2.检查构建结果
执行完之后,查看一下镜像是否已经被创建出来(会自动被放到Docker的本地镜像目录)
bash
docker images

3.3.测试运行
可以看到,已经被制作出来了。
然后运行一下这个镜像/容器
bash
# 运行容器
docker run -p 5000:5000 flask-minimal:latest

然后在网页访问一下:

没毛病。
接下来就可以部署了。
(在实际中,我们最好还得用Nuitka或者别的工具对我们的Python源码或者其他资源处理一下,不能直接把工程打包到镜像中,否则客户就可以直接获取到我们的源码了。)
4.部署
我们在创建好之后镜像后,我们得把这个镜像搞成一个文件,方便我们发给用户。
使用下面的命令来把镜像打包
bash
# 语法:docker save -o 输出的文件名.tar 镜像名:标签
docker save -o flask-minimal.tar flask-minimal:latest
执行成功的话,可以在当前文件夹下看到flask-minimal.tar文件

然后将此文件拷贝到在客户的电脑上,导入此镜像
bash
# 语法:docker load -i 文件名.tar
docker load -i flask-minimal.tar
然后就可以执行这个镜像了。
至此,一个制作自己程序的Docker镜像并发布给客户使用的流程就完成了。
5.高级操作
5.1.打包conda环境
我实际的Flask项目比较复杂,需要用到很多Python包,因此,我使用到了conda来管理。在制作Docker镜像时,可以选择将Conda环境打包进去。
在打包Conda镜像时,有两种方式:一种是通过导出conda环境的配置文件,然后在镜像中按照这份环境配置重现下载包来搭建一个镜像内部的环境;另外一种是直接拷贝本地的conda环境文件。
前一种方式可以实现跨平台部署(比如在Mac系统下构建Ubuntu平台的镜像),但是需要全量重新下载对应conda环境中的包,一次耗时要比较久。
而我是在Ubuntu上构建Ubuntu的镜像,也就是开发机器和目标机器的架构一样,所以我选择第二种方式就行,这种快。
Dockerfile的内容
bash
# 使用一个精简的Miniconda基础镜像
FROM continuumio/miniconda3:latest
# 将本地的Conda环境整个复制到镜像中
# 注意:这里的路径需要替换为你实际的conda环境的路径
COPY ./my_env /opt/conda/envs/my_env
# 或者,更通用的做法是先将环境打包成tar.gz,再在Dockerfile中解压
# ADD my_env.tar.gz /opt/conda/envs/
# 设置容器启动时,默认激活这个环境
SHELL ["conda", "run", "-n", "my_env", "/bin/bash", "-c"]
# 验证环境是否可用
RUN echo "Making sure environment is installed correctly..." && \
conda list -n my_env
# 设置你的工作目录和启动命令
WORKDIR /app
COPY . /app
CMD ["python", "your_app.py"]
这种"复制"方法非常快捷,但有其特定的适用场景和需要注意的"坑":
- 系统兼容性:这是最主要的风险。如果在 macOS 或 Windows 上创建的环境,直接复制到基于 Linux 的 Docker 镜像中,可能会因为底层二进制文件(尤其是包含C扩展的包,如 numpy, pandas, tensorflow)不兼容而导致程序无法运行或崩溃。此方法最适用于构建镜像的宿主机与镜像本身是相同操作系统架构(通常是Linux)的情况。
- 环境纯净度:复制过去的是完整的 envs/my_env 目录,包含所有pip和conda的缓存文件。如果不需要,可以在复制后,在Dockerfile里运行 conda clean -a 和 pip cache purge 来清理,以减小镜像体积。
前面一种方式的Dockerfile
bash
# 1. 使用官方Miniconda镜像作为基础
FROM continuumio/miniconda3
# 2. 设置工作目录
WORKDIR /app
# 3. 将环境配置文件复制到镜像中
COPY environment.yml .
# 4. 根据配置文件创建Conda环境
RUN conda env create -f environment.yml
# 5. 在容器中激活环境,并设置后续命令的运行环境
# 注意:在Dockerfile中不能直接使用 `conda activate`,这是关键细节
SHELL ["conda", "run", "-n", "your_env_name", "/bin/bash", "-c"]
# 6. 可以安装一些额外的系统依赖(如果需要)
# RUN apt-get update && apt-get install -y some-package
# 7. 复制你的应用代码(假设代码在当前目录)
COPY . .
# 8. 设置容器启动时默认执行的命令
CMD ["conda", "run", "-n", "your_env_name", "python", "your_app.py"]
5.2.查看容器列表
bash
docker ps -a

5.3.查看容器内的文件
如何进入一个正在运行的容器 (用于调试、查看日志、临时修改)?
可以使用 docker exec 命令可以启动一个额外的进程(通常是shell)进入容器:
bash
# 1. 创建并启动容器
docker run -d -p 5000:5000 --name my-flask flask-minimal:latest
# 2. 使用 exec 命令进入容器的 shell 环境
docker exec -it my-flask /bin/sh # Alpine 用 /bin/sh
# 或如果是基于Debian/Ubuntu的镜像
# docker exec -it my-flask /bin/bash
进入后,你就可以像在一台小型Linux机器里一样,使用 ls, cd, cat, vi 等命令查看或修改文件。
但是,注意:修改的只是容器的内容,而不是镜像的内容!一旦容器停止并删除,所有修改都会丢失。但是只要你不删除这个容器,而是简单的停止/启动,这些修改的文件都还在的

镜像就像是一个模板,基于这个模板我们可以实例化多个容器,容器就像是一个个独立运行的linux机器,你可以在其里面进行各种操作。
5.4. docker run / docker start的区别
| 命令 | docker run | docker start |
|---|---|---|
| 作用 | 创建并启动新容器 | 启动已存在的停止的容器 |
| 容器数量 | 每次运行都会创建新容器 | 不创建新容器,只启动现有容器 |
| 创建过程 | 从镜像创建容器(类似 create + start) | 不创建,只启动 |
| 配置 | 可以指定所有配置选项(端口、卷等) | 只能指定基础选项(如后台运行) |
| 使用频率 | 每个容器只用一次 | 可以多次使用(启动/停止) |
docker run 特有的参数(只能在创建时指定):
bash
# 这些参数只能在 run 时设置
docker run \
--name myapp \ # 容器名称
-p 8080:80 \ # 端口映射
-v /data:/app/data \ # 卷挂载
-e ENV=production \ # 环境变量
--network mynet \ # 网络
--restart always \ # 重启策略
nginx:latest
在使用挂载时 -v /data:/app/data需要注意,左侧为宿主机的目录,右侧为容器的目录,假如容器内的此目录原本有东西,会被宿主机的此目录完全覆盖。
也就是相当于docker run是根据模板创建程序实例,而docker start是启动程序实例。