1. 为什么你需要Dockerfile?

如果你曾经遇到过"在我电脑上能跑,怎么到你那就报错"的情况,Dockerfile就是你的救星。想象一下,你开发了一个简单的天气预报网页,想在朋友的电脑上展示,结果发现他的Python版本不对、缺少依赖库、端口被占用... 这时候Dockerfile就像个魔法盒子,把应用和它的运行环境一起打包,在任何支持Docker的机器上都能一键运行。

我去年帮学弟调试课程项目时就深有体会。他们用Flask写了个学生管理系统,在实验室电脑运行正常,但提交作业时老师的服务器环境不同导致报错。后来我们用Dockerfile重新打包,再也没出现过环境问题。

2. 准备你的第一个Web应用

2.1 选择适合入门的项目

我们从最简单的Python Flask应用开始,它只需要两个文件:

  • app.py :主程序文件
  • requirements.txt :依赖清单

创建一个项目文件夹,比如叫 my_web_app ,然后新建 app.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "恭喜!你的第一个Dockerized Web应用运行成功!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

再创建 requirements.txt

flask==2.0.1

这个微型应用会在访问首页时返回一句欢迎语。虽然简单,但包含了Web服务的基本要素:路由、响应和端口监听。

2.2 验证应用能否本地运行

在继续之前,我们先确保应用能在本地正常工作:

pip install -r requirements.txt
python app.py

打开浏览器访问 http://localhost:5000 ,应该能看到欢迎消息。记住这个端口号5000,稍后写Dockerfile时会用到。

3. 编写你的第一个Dockerfile

3.1 理解基础指令

现在来到重头戏——在项目根目录创建 Dockerfile 文件(注意没有扩展名)。这个文件就像烹饪食谱,告诉Docker如何"烹制"你的应用镜像。

# 使用官方Python镜像作为基础
FROM python:3.9-slim

# 设置工作目录(容器内的路径)
WORKDIR /app

# 先复制依赖文件(利用Docker缓存层优化构建速度)
COPY requirements.txt .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制所有项目文件
COPY . .

# 暴露应用运行的端口(与app.py中的端口一致)
EXPOSE 5000

# 定义启动命令
CMD ["python", "app.py"]

这个Dockerfile做了以下几件事:

  1. 选择了一个轻量级的Python 3.9镜像作为基础
  2. 在容器内创建了 /app 工作目录
  3. 分两步复制文件(先requirements.txt再其他文件),这是Docker构建优化的小技巧
  4. 安装了Flask依赖
  5. 声明容器运行时暴露的端口
  6. 定义了启动命令

3.2 那些我踩过的坑

刚开始写Dockerfile时,我经常犯这两个错误:

  1. 忘记EXPOSE指令 :虽然不影响运行,但会让其他人不知道应该映射哪个端口
  2. 使用太庞大的基础镜像 :比如直接用 python:3.9 而不是 python:3.9-slim ,导致镜像体积大了好几倍

另一个实用技巧是 .dockerignore 文件。像 .gitignore 一样,它可以避免把不必要的文件(如 __pycache__ .env 等)复制到镜像中,减小镜像体积。在项目根目录创建它:

__pycache__
*.pyc
.env
.DS_Store

4. 构建和运行你的Docker镜像

4.1 构建镜像

在Dockerfile所在目录运行:

docker build -t my-web-app .

这个命令会:

  1. 读取当前目录的Dockerfile
  2. 按照指令一步步构建镜像
  3. 最后给镜像打上标签 my-web-app

第一次构建可能会慢一些,因为要下载基础镜像。后续构建会利用缓存,速度飞快。

4.2 运行容器

镜像构建完成后,用这个命令运行它:

docker run -d -p 5000:5000 --name my-app my-web-app

参数说明:

  • -d :后台运行(detached模式)
  • -p 5000:5000 :把主机的5000端口映射到容器的5000端口
  • --name :给容器起个名字(方便后续管理)

现在再次访问 http://localhost:5000 ,应该能看到同样的欢迎消息,但这次是从Docker容器提供的服务!

4.3 管理你的容器

几个常用命令:

# 查看运行中的容器
docker ps

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

# 停止容器
docker stop my-app

# 启动已停止的容器
docker start my-app

# 删除容器
docker rm my-app

# 删除镜像
docker rmi my-web-app

5. 进阶技巧与最佳实践

5.1 多阶段构建

当你的应用需要编译时(比如前端项目),可以使用多阶段构建来减小最终镜像大小。这里以Node.js项目为例:

# 第一阶段:构建阶段
FROM node:14 as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 第二阶段:运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

这种构建方式最终只包含nginx和编译好的静态文件,去掉了庞大的node_modules。

5.2 环境变量管理

不要在Dockerfile中硬编码敏感信息!使用 ARG ENV

# 构建时传入的变量
ARG APP_VERSION=1.0

# 运行时环境变量
ENV FLASK_ENV=production

构建时可以通过 --build-arg 传递变量:

docker build --build-arg APP_VERSION=2.0 -t my-web-app .

5.3 健康检查

添加健康检查指令可以让Docker监控你的应用状态:

HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:5000/ || exit 1

这样运行 docker ps 时可以看到容器健康状态,编排工具也能据此做出反应。

6. 实际项目中的经验分享

去年我用Docker部署了一个机器学习API服务,总结了几条实用经验:

  1. 日志管理 :默认情况下容器日志会堆积,建议:

    docker run --log-opt max-size=10m --log-opt max-file=3 ...
    
  2. 时区问题 :容器内默认是UTC时间,可以这样设置:

    ENV TZ=Asia/Shanghai
    RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
    
  3. 数据持久化 :对需要保存的数据使用volume:

    docker run -v /path/on/host:/path/in/container ...
    
  4. 资源限制 :防止容器占用过多资源:

    docker run --memory=512m --cpus=1 ...
    

7. 调试技巧

当容器行为不符合预期时,可以:

  1. 查看日志:

    docker logs my-app
    
  2. 进入运行中的容器检查:

    docker exec -it my-app /bin/bash
    
  3. 如果容器启动失败,去掉 -d 参数直接在前台运行,查看实时输出

  4. 使用 docker inspect my-app 查看容器详细配置

记得有一次我的Flask应用在容器内启动失败,就是因为没把host设为 0.0.0.0 (容器内默认的localhost与主机不互通),这个小细节折腾了我半小时。

更多推荐