强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Dockerfile 写作精讲 / 07 - EXPOSE 与端口

07 - EXPOSE 与端口:端口声明、映射与健康检查

7.1 EXPOSE 指令概述

EXPOSE 指令用于声明容器运行时监听的端口。它是一个文档性指令,不会实际发布端口

# 声明容器监听 8080 端口
EXPOSE 8080

# 声明 TCP 和 UDP
EXPOSE 8080/tcp
EXPOSE 5353/udp

# 同时声明多个端口
EXPOSE 8080 8443 9090

EXPOSE 的真正作用

作用说明
文档声明告诉使用者容器监听哪些端口
配合 -Pdocker run -P 会自动映射 EXPOSE 的端口到随机主机端口
配合 Composedocker-compose 可以根据 EXPOSE 自动生成端口配置

EXPOSE 不等于端口映射

EXPOSE 8080
# EXPOSE 不会发布端口!
docker run myapp
# 主机无法访问 8080

# 需要使用 -p 显式映射
docker run -p 8080:8080 myapp

# 使用 -P 自动映射 EXPOSE 的端口
docker run -P myapp
# 自动映射: 主机随机端口 → 容器 8080
docker port <container_id>
# 输出: 8080/tcp -> 0.0.0.0:49153

7.2 端口映射详解

-p 参数的多种形式

# 映射到指定主机端口
docker run -p 8080:80 myapp

# 映射到指定 IP 和端口
docker run -p 127.0.0.1:8080:80 myapp

# 映射到随机主机端口
docker run -p 80 myapp

# 映射 UDP 端口
docker run -p 5353:5353/udp myapp

# 同时映射 TCP 和 UDP
docker run -p 5353:5353/tcp -p 5353:5353/udp myapp

# 映射端口范围
docker run -p 8000-8100:8000-8100 myapp

端口映射网络流向

客户端请求 → 主机 IP:主机端口 → Docker NAT → 容器 IP:容器端口
                                    ↕
                              iptables / 用户态代理

注意事项:默认情况下 Docker 使用 iptables 进行端口转发。在生产环境中,建议使用 Docker 的 userland-proxy=false 配置以提高性能。

7.3 多端口应用

前后端分离

FROM nginx:alpine

# 前端静态文件
COPY dist/ /usr/share/nginx/html/

# Nginx 配置:反向代理 API
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 前端端口
EXPOSE 80

# 如果同时暴露管理端口
EXPOSE 8080

多服务容器(不推荐但有时需要)

FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    nginx \
    python3 \
    supervisor \
    && rm -rf /var/lib/apt/lists/*

COPY nginx.conf /etc/nginx/
COPY app.py /app/
COPY supervisord.conf /etc/supervisor/

# Web 端口
EXPOSE 80
# 管理端口
EXPOSE 9001

CMD ["/usr/bin/supervisord"]

7.4 HEALTHCHECK 指令

HEALTHCHECK 指令告诉 Docker 如何检测容器是否健康运行。

基本语法

# 健康检查
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1

# 带参数
HEALTHCHECK \
    --interval=30s \
    --timeout=10s \
    --start-period=5s \
    --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 禁用健康检查
HEALTHCHECK NONE

参数说明

参数默认值说明
--interval30s两次检查之间的间隔
--timeout30s单次检查超时时间
--start-period0s容器启动后的宽限期(此期间失败不计入重试次数)
--retries3连续失败几次后标记为 unhealthy

不同应用的健康检查

# Web 服务
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost:8080/health || exit 1

# 数据库(PostgreSQL)
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
    CMD pg_isready -U postgres || exit 1

# Redis
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
    CMD redis-cli ping || exit 1

# 自定义脚本
HEALTHCHECK --interval=30s --timeout=10s \
    CMD /healthcheck.sh

不使用 curl 的健康检查

# 使用 wget(Alpine 默认没有 curl)
HEALTHCHECK --interval=30s --timeout=3s \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

# 使用 netcat
HEALTHCHECK --interval=30s --timeout=3s \
    CMD nc -z localhost 8080 || exit 1

# 使用 Python
HEALTHCHECK --interval=30s --timeout=3s \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1

# 使用 Node.js
HEALTHCHECK --interval=30s --timeout=3s \
    CMD node -e "require('http').get('http://localhost:8080/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"

查看健康检查状态

# 查看容器健康状态
docker inspect --format='{{.State.Health.Status}}' mycontainer

# 查看健康检查日志
docker inspect --format='{{json .State.Health}}' mycontainer | jq

# docker ps 中显示健康状态
docker ps
# STATUS 列会显示 healthy / unhealthy / starting

7.5 Docker Compose 中的端口与健康检查

services:
  web:
    build: .
    ports:
      - "8080:80"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    image: postgres:16
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

7.6 实战:完整的端口与健康检查配置

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# 创建非 root 用户
RUN addgroup -g 1001 appuser && \
    adduser -u 1001 -G appuser -s /bin/sh -D appuser && \
    chown -R appuser:appuser /app

USER appuser

# 声明端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

7.7 常见错误与排查

错误原因解决方案
端口无法访问EXPOSE 不发布端口使用 -p 映射端口
port is already allocated端口被占用更换端口或停止占用进程
健康检查始终 unhealthy检查命令错误手动在容器内执行检查命令
健康检查启动期失败start-period 太短增加 start-period 值
-P 无法映射未使用 EXPOSE添加 EXPOSE 指令
localhost 拒绝连接应用绑定 127.0.0.1绑定 0.0.0.0

7.8 扩展阅读


上一章06 - CMD 与 ENTRYPOINT 下一章08 - USER 与权限 — 非 root 运行、权限管理与文件所有权。