Skip to content

Dockerfile 基础

Dockerfile 是一个文本文件,包含一系列指令,Docker 按顺序执行这些指令来构建镜像。

第一个 Dockerfile

以一个简单的 Node.js 应用为例:

项目结构:

myapp/
├── Dockerfile
├── package.json
└── app.js

app.js:

javascript
const http = require('http');
const server = http.createServer((req, res) => {
  res.end('Hello from Docker!\n');
});
server.listen(3000, () => console.log('Server running on port 3000'));

package.json:

json
{
  "name": "myapp",
  "version": "1.0.0",
  "main": "app.js"
}

Dockerfile:

dockerfile
# 基础镜像
FROM node:20-alpine

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY package*.json ./

# 安装依赖
RUN npm install

# 复制源码
COPY . .

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", "app.js"]

构建和运行

bash
# 在 Dockerfile 所在目录执行构建
docker build -t myapp:1.0 .

# 查看构建结果
docker images myapp

# 运行容器
docker run -d -p 3000:3000 --name myapp myapp:1.0

# 测试
curl localhost:3000

基础指令详解

FROM — 指定基础镜像

每个 Dockerfile 必须以 FROM 指令开始(ARG 除外):

dockerfile
# 使用官方镜像
FROM ubuntu:22.04
FROM node:20-alpine
FROM python:3.12-slim

# 使用 scratch(空白镜像,用于静态编译的程序)
FROM scratch

# 多阶段构建中的 FROM
FROM golang:1.21 AS builder
FROM alpine:3.19 AS runtime

WORKDIR — 设置工作目录

dockerfile
# 设置后续指令的工作目录(不存在会自动创建)
WORKDIR /app

# 相对路径(相对于上一个 WORKDIR)
WORKDIR /app
WORKDIR src   # 实际路径为 /app/src

COPY — 复制文件

dockerfile
# 复制单个文件
COPY app.js /app/app.js

# 复制到工作目录(WORKDIR)
COPY package.json .

# 复制多个文件
COPY package.json package-lock.json ./

# 使用通配符
COPY *.json ./
COPY src/ ./src/

# 保留文件权限(--chmod)
COPY --chmod=755 entrypoint.sh /entrypoint.sh

ADD — 添加文件(扩展版 COPY)

dockerfile
# 与 COPY 类似,但支持:
# 1. URL 下载
ADD https://example.com/file.tar.gz /app/

# 2. 自动解压 tar 文件
ADD source.tar.gz /app/

推荐:大多数情况下使用 COPY,它更透明。只有需要解压 tar 或下载 URL 时才用 ADD

RUN — 执行命令

dockerfile
# 执行 shell 命令(/bin/sh -c)
RUN apt-get update && apt-get install -y curl

# exec 格式(推荐,避免 shell 解析问题)
RUN ["apt-get", "install", "-y", "curl"]

# 多行命令(推荐合并到一条 RUN,减少层数)
RUN apt-get update \
    && apt-get install -y \
       curl \
       vim \
       git \
    && rm -rf /var/lib/apt/lists/*

CMD — 容器启动命令

dockerfile
# exec 格式(推荐)
CMD ["node", "app.js"]
CMD ["nginx", "-g", "daemon off;"]

# shell 格式
CMD node app.js

# CMD 提供默认命令,可以被 docker run 的命令参数覆盖
# docker run myapp node other.js  # 会覆盖 CMD

ENTRYPOINT — 入口点

dockerfile
# exec 格式(推荐)
ENTRYPOINT ["node"]
CMD ["app.js"]  # 作为 ENTRYPOINT 的默认参数

# ENTRYPOINT 不会被 docker run 的命令参数覆盖
# docker run myapp other.js  # 实际执行:node other.js

CMD vs ENTRYPOINT 的区别:

CMDENTRYPOINT
能否被覆盖可以(docker run 后跟命令)不能(需要 --entrypoint 才能覆盖)
组合使用作为 ENTRYPOINT 的默认参数定义容器的主命令
推荐场景提供默认参数容器作为命令工具时

EXPOSE — 声明端口

dockerfile
# 声明容器监听的端口(仅文档作用,不会自动映射)
EXPOSE 3000
EXPOSE 80 443
EXPOSE 8080/tcp 8080/udp

ENV — 环境变量

dockerfile
# 设置环境变量(构建时和运行时都可用)
ENV NODE_ENV=production
ENV PORT=3000

# 多个变量(推荐单条 ENV 声明多个)
ENV NODE_ENV=production \
    PORT=3000 \
    APP_NAME=myapp

ARG — 构建参数

dockerfile
# 只在构建时可用,不会保留到最终镜像
ARG VERSION=1.0
ARG BUILD_DATE

# 使用构建参数
FROM node:${VERSION}-alpine

# 构建时传入参数
# docker build --build-arg VERSION=20 .

完整示例:Python Flask 应用

dockerfile
FROM python:3.12-slim

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PORT=5000

WORKDIR /app

# 先复制依赖文件(利用缓存)
COPY requirements.txt .

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

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 5000

# 使用非 root 用户运行
RUN addgroup --system appgroup && adduser --system --group appuser
USER appuser

CMD ["python", "app.py"]

.dockerignore 文件

类似 .gitignore,排除不需要复制到镜像的文件:

# .dockerignore
node_modules/
.git/
.gitignore
*.log
.env
dist/
coverage/
__pycache__/
*.pyc
.DS_Store
README.md

查看构建过程

bash
# 构建镜像(显示详细输出)
docker build -t myapp:1.0 .

# 构建时显示所有中间层
docker build --no-cache -t myapp:1.0 .

# 查看镜像分层
docker history myapp:1.0

总结

Dockerfile 的核心指令:

指令作用
FROM指定基础镜像
WORKDIR设置工作目录
COPY复制文件
RUN执行命令(构建阶段)
CMD容器启动的默认命令
ENTRYPOINT容器入口点
EXPOSE声明端口
ENV设置环境变量
ARG构建参数

下一节介绍所有 Dockerfile 指令的详细用法。