长安的Docker教程长安的Docker教程
首页
快速开始
编程指南
首页
快速开始
编程指南
  • 🎯 Docker入门篇

    • 快速开始
    • Docker是什么?
    • 为什么要用Docker?
    • 安装Docker
    • 第一个容器
  • 📦 Docker基础篇

    • Docker镜像详解
    • 容器操作详解
    • 编写Dockerfile
    • 数据卷(Volumes)
    • Docker网络
  • ⚡ Docker进阶篇

    • Docker Compose
    • 多阶段构建
    • Docker性能优化
    • Docker最佳实践
  • 🚀 实战项目

    • 实战:部署Node.js应用
    • 实战:搭建数据库环境
    • 实战:微服务架构
  • 💡 常见问题

    • 常见问题排查
    • 实用技巧

编写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 --interval=30s --timeout=3s --start-period=5s --retries=3 \
    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 --from=builder /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 --from=builder /app/dist ./dist
COPY --from=builder /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 --interval=30s --timeout=3s --retries=3 \
    CMD curl -f http://localhost/ || exit 1

🎯 小结

今天长安教你玩转Dockerfile:

核心指令

FROM       # 基础镜像
WORKDIR    # 工作目录
COPY/ADD   # 复制文件
RUN        # 执行命令
CMD        # 启动命令
ENTRYPOINT # 入口点
EXPOSE     # 声明端口
ENV        # 环境变量
ARG        # 构建参数

最佳实践

  1. 使用.dockerignore
  2. 合并RUN指令
  3. 利用构建缓存
  4. 使用多阶段构建
  5. 不用root用户
  6. 使用明确的版本标签

🚀 下一步

会写Dockerfile了,但数据怎么持久化?下一章长安教你 数据卷管理!


💬 长安的踩坑经验:

我第一次写Dockerfile的时候,镜像大小2GB+,老板看了差点打我😂

后来学会了多阶段构建,镜像瘦身到50MB,老板夸我聪明!

还有一次,我把数据库密码写在Dockerfile里提交到Git,差点出大事...

记住:敏感信息永远不要写在Dockerfile里!

慢慢来,多练习,写Dockerfile会越来越顺手的!💪

在 GitHub 上编辑此页
Prev
容器操作详解
Next
数据卷(Volumes)