编写Dockerfile
嘿!长安来教你写Dockerfile了!这是Docker最重要的技能之一,学会了你就能制作自己的镜像!🎨
🤔 Dockerfile是什么?
简单的说
Dockerfile就是制作镜像的"菜谱",里面写着一步一步的指令,告诉Docker怎么构建镜像。
Dockerfile = 菜谱
docker build = 做菜
镜像 = 做好的菜
一个简单的例子
# Dockerfile
FROM ubuntu:22.04
RUN apt update && apt install -y nginx
CMD ["nginx", "-g", "daemon off;"]
这三行就创建了一个nginx镜像!
📝 第一个Dockerfile
让长安带你写第一个Dockerfile:
创建项目
# 创建目录
mkdir my-first-docker
cd my-first-docker
# 创建Dockerfile
touch Dockerfile
编写Dockerfile
# 使用官方Python镜像作为基础
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 复制当前目录的文件到容器
COPY . /app
# 安装依赖
RUN pip install --no-cache-dir flask
# 暴露端口
EXPOSE 5000
# 运行应用
CMD ["python", "app.py"]
创建Python应用
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "嘿!我是长安,这是我的第一个Docker镜像!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
构建镜像
docker build -t my-python-app .
解释:
docker build:构建镜像-t my-python-app:给镜像打标签.:Dockerfile所在目录(当前目录)
你会看到:
[+] Building 15.2s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 250B
=> [internal] load .dockerignore
=> [internal] load metadata for docker.io/library/python:3.11-slim
=> [1/4] FROM docker.io/library/python:3.11-slim
=> [internal] load build context
=> => transferring context: 125B
=> [2/4] WORKDIR /app
=> [3/4] COPY . /app
=> [4/4] RUN pip install --no-cache-dir flask
=> exporting to image
=> => exporting layers
=> => writing image sha256:abc123...
=> => naming to docker.io/library/my-python-app
运行镜像
docker run -d -p 5000:5000 --name my-app my-python-app
测试
打开浏览器访问:http://localhost:5000
看到长安的问候了吗?成功! 🎉
📚 Dockerfile指令详解
FROM - 基础镜像
必须是第一条指令(除了注释)
# 使用官方镜像
FROM python:3.11
# 使用特定版本
FROM node:18.17.0
# 使用Alpine版本(更小)
FROM python:3.11-alpine
# 使用scratch(空镜像,用于打包已编译的程序)
FROM scratch
💡 长安建议:优先选择官方镜像,选择slim或alpine版本更小
WORKDIR - 工作目录
设置后续指令的工作目录
WORKDIR /app
# 相当于
RUN cd /app
好处:
- 所有相对路径都基于这个目录
- 目录不存在会自动创建
- 进入容器时默认就在这个目录
COPY - 复制文件
从宿主机复制文件到镜像
# 复制文件
COPY app.py /app/
# 复制目录
COPY ./src /app/src
# 复制多个文件
COPY package.json package-lock.json /app/
# 复制所有文件
COPY . /app/
# 复制并重命名
COPY config.json /app/config/app-config.json
ADD - 复制并解压
和COPY类似,但功能更强大(也更复杂)
# 复制文件(和COPY一样)
ADD app.py /app/
# 自动解压tar文件
ADD archive.tar.gz /app/
# 从URL下载(不推荐)
ADD https://example.com/file.txt /app/
💡 长安建议:大多数情况用COPY,除非需要解压tar文件
RUN - 执行命令
在镜像构建时执行命令
# Shell格式(会启动shell)
RUN apt update && apt install -y nginx
# Exec格式(不启动shell,更高效)
RUN ["apt", "update"]
# 多行写法(推荐!)
RUN apt update && \
apt install -y \
nginx \
curl \
vim && \
apt clean && \
rm -rf /var/lib/apt/lists/*
最佳实践:
# ❌ 不好(每个RUN会创建一层)
RUN apt update
RUN apt install -y nginx
RUN apt install -y curl
RUN apt clean
# ✅ 好(合并成一层)
RUN apt update && \
apt install -y nginx curl && \
apt clean
CMD - 容器启动命令
容器启动时执行的默认命令
# Shell格式
CMD python app.py
# Exec格式(推荐!)
CMD ["python", "app.py"]
# 和ENTRYPOINT配合使用
CMD ["--help"]
注意:
- Dockerfile只能有一个CMD
- 如果有多个CMD,只有最后一个生效
docker run后面的命令会覆盖CMD
ENTRYPOINT - 入口点
和CMD类似,但不会被docker run后面的命令覆盖
# Shell格式
ENTRYPOINT python app.py
# Exec格式(推荐!)
ENTRYPOINT ["python", "app.py"]
CMD和ENTRYPOINT的组合:
# 示例1:ENTRYPOINT固定命令,CMD提供参数
ENTRYPOINT ["python"]
CMD ["app.py"]
# 运行容器
docker run my-image # 执行:python app.py
docker run my-image test.py # 执行:python test.py
# 示例2:制作可执行的镜像
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
💡 长安的理解:ENTRYPOINT是主命令,CMD是默认参数
EXPOSE - 声明端口
声明容器运行时监听的端口
# 声明单个端口
EXPOSE 80
# 声明多个端口
EXPOSE 80 443
# 声明UDP端口
EXPOSE 53/udp
注意:
- EXPOSE只是声明,不会实际发布端口
- 需要用
-p参数才能真正映射端口
docker run -p 8080:80 my-image
ENV - 环境变量
设置环境变量
# 设置单个变量
ENV APP_ENV=production
# 设置多个变量
ENV APP_ENV=production \
APP_DEBUG=false \
APP_PORT=8080
# 在RUN中使用
ENV NODE_VERSION=18.17.0
RUN curl -o /tmp/node.tar.gz \
"https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}.tar.gz"
ARG - 构建参数
构建时传入的参数(只在构建时有效)
# 定义参数
ARG NODE_VERSION=18.17.0
ARG APP_ENV=development
# 使用参数
FROM node:${NODE_VERSION}
RUN echo "Building for ${APP_ENV}"
构建时传入:
docker build \
--build-arg NODE_VERSION=20.0.0 \
--build-arg APP_ENV=production \
-t my-app .
ARG vs ENV:
- ARG:只在构建时有效
- ENV:在构建和运行时都有效
VOLUME - 数据卷
声明挂载点
VOLUME /data
VOLUME /var/log /var/db
USER - 切换用户
指定运行容器时的用户
# 使用root用户(默认)
USER root
# 切换到其他用户
USER nginx
# 使用UID
USER 1000
最佳实践:
# 创建用户并切换
RUN useradd -m -s /bin/bash appuser && \
chown -R appuser:appuser /app
USER appuser
💡 长安建议:生产环境不要用root用户运行容器!
LABEL - 添加元数据
给镜像添加标签
LABEL version="1.0"
LABEL maintainer="长安 <changan@example.com>"
LABEL description="这是长安的Docker镜像"
# 多个标签
LABEL version="1.0" \
maintainer="长安" \
description="Docker教程示例"
🎨 实战案例
案例1:Node.js应用
项目结构:
my-node-app/
├── Dockerfile
├── package.json
├── package-lock.json
└── app.js
Dockerfile:
# 使用Node.js官方镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 暴露端口
EXPOSE 3000
# 创建非root用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
USER nodejs
# 启动应用
CMD ["node", "app.js"]
构建:
docker build -t my-node-app .
docker run -d -p 3000:3000 my-node-app
案例2:Python Flask应用
Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# 安装系统依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制源代码
COPY . .
# 创建非root用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
案例3:静态网站(Nginx)
Dockerfile:
FROM nginx:alpine
# 复制网站文件
COPY ./html /usr/share/nginx/html
# 复制自定义nginx配置
COPY ./nginx.conf /etc/nginx/nginx.conf
# 健康检查
HEALTHCHECK \
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
🚀 多阶段构建
超级重要的技巧! 可以大幅减小镜像大小。
问题场景
比如编译Go程序:
# ❌ 单阶段构建(镜像很大)
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
# 结果:镜像大小 800MB+(包含整个Go环境)
解决方案:多阶段构建
# ✅ 多阶段构建(镜像很小)
# 阶段1:编译阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 阶段2:运行阶段
FROM alpine:latest
WORKDIR /app
COPY /app/myapp .
CMD ["./myapp"]
# 结果:镜像大小 10MB(只包含可执行文件)
减少了98%的大小! 🎉
Node.js多阶段构建
# 阶段1:构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 阶段2:运行阶段
FROM node:18-alpine
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY package.json .
EXPOSE 3000
CMD ["node", "dist/index.js"]
💡 Dockerfile最佳实践
1. 使用.dockerignore
创建.dockerignore文件(类似.gitignore):
# .dockerignore
node_modules
npm-debug.log
.git
.env
*.md
.DS_Store
2. 合并RUN指令
# ❌ 不好(多层)
RUN apt update
RUN apt install -y nginx
RUN apt clean
# ✅ 好(一层)
RUN apt update && \
apt install -y nginx && \
apt clean && \
rm -rf /var/lib/apt/lists/*
3. 利用构建缓存
# ✅ 先复制依赖文件,再复制代码
COPY package.json .
RUN npm install
COPY . .
# 这样代码改了,npm install不会重新执行
4. 使用明确的标签
# ❌ 不好
FROM python:latest
# ✅ 好
FROM python:3.11-slim
5. 最小化层数
# ❌ 不好(4层)
FROM ubuntu
RUN apt update
RUN apt install -y python3
RUN apt install -y pip
# ✅ 好(2层)
FROM ubuntu
RUN apt update && apt install -y python3 pip
6. 不要在镜像中存储敏感信息
# ❌ 危险!
ENV DATABASE_PASSWORD=123456
# ✅ 安全(运行时传入)
docker run -e DATABASE_PASSWORD=123456 my-app
7. 健康检查
HEALTHCHECK \
CMD curl -f http://localhost/ || exit 1
🎯 小结
今天长安教你玩转Dockerfile:
核心指令
FROM # 基础镜像
WORKDIR # 工作目录
COPY/ADD # 复制文件
RUN # 执行命令
CMD # 启动命令
ENTRYPOINT # 入口点
EXPOSE # 声明端口
ENV # 环境变量
ARG # 构建参数
最佳实践
- 使用
.dockerignore - 合并RUN指令
- 利用构建缓存
- 使用多阶段构建
- 不用root用户
- 使用明确的版本标签
🚀 下一步
会写Dockerfile了,但数据怎么持久化?下一章长安教你 数据卷管理!
💬 长安的踩坑经验:
我第一次写Dockerfile的时候,镜像大小2GB+,老板看了差点打我😂
后来学会了多阶段构建,镜像瘦身到50MB,老板夸我聪明!
还有一次,我把数据库密码写在Dockerfile里提交到Git,差点出大事...
记住:敏感信息永远不要写在Dockerfile里!
慢慢来,多练习,写Dockerfile会越来越顺手的!💪
