目录
4.1-核心思想:为什么要分 “builder” 和 “runtime” 阶段?
4.2-示例 A:Go 语言多阶段构建解析(静态编译到 scratch)
4.3-示例 B:Node(构建前端产物,Nginx 提供)
5-语言生态的依赖缓存技巧(Python + Node 两种)
package-lock.json(执行 npm install 自动生成,锁定依赖版本)
敏感密钥文件:.npmrc.secret(BuildKit 安全挂载,不进镜像)
构建优化文件:.dockerignore(减少上下文,排除敏感文件)
项目源码:server.js(简单 HTTP 服务,用于验证)
1-常用命令速查
# 版本与环境
docker version
docker info
# 搜索/拉取/查看镜像
docker search nginx
docker pull nginx:alpine
docker images
docker rmi <image-id>
# 运行容器(-d 后台,-p 端口映射,--name 命名)
docker run -d -p 8080:80 --name web nginx:alpine
# 日志/进入容器/查看进程/资源使用
docker logs -f web
docker exec -it web sh
docker top web
docker stats
# 停止/启动/重启/删除
docker stop web
docker start web
docker restart web
docker rm -f web
# 导出/导入
docker save nginx:alpine > nginx.tar
docker load < nginx.tar
2-基础概念与最小可用 Dockerfile
2.1-最小必备指令
FROM:基础镜像(尽量选*-alpine或官方 slim 版)
WORKDIR:设置工作目录
COPY/ADD:复制文件到镜像(优先COPY)
RUN:构建期执行命令(如安装依赖)
EXPOSE:声明容器内部端口(信息化,不做映射)
ENV:环境变量
HEALTHCHECK:健康检查(对生产很重要)
ENTRYPOINT/CMD:容器启动命令(推荐:ENTRYPOINT为程序,CMD为默认参数)
2.2-Dockerfile示例
docker pull alpine:3.20
mkdir hello && cd hello
tee > hello.sh << "EOF"
#!/bin/sh
# 输出简单的问候信息
echo "Hello, World!"
echo "This is running inside an Alpine container."
# 可以添加更多信息,比如当前时间
echo "Current time: $(date)"
# 显示工作目录
echo "Working directory: $(pwd)"
EOF
2.2.1-Dockerfile创建镜像
tee > dockerfile << "EOF"
FROM alpine:3.20
WORKDIR /app
COPY hello.sh .
RUN chmod +x hello.sh
CMD ["./hello.sh"]
EOF
docker build -t hello .
docker run hello

2.2.2-BuildKit介绍
关键词:镜像、层(layer)、构建上下文(build context)、BuildKit 缓存
-
镜像与层:Dockerfile 中的每条指令(如
RUN/COPY)都会生成一层;层是可缓存、可复用的。 -
构建上下文:
docker build发送当前目录的全部内容到 Docker 守护进程;用.dockerignore排除无关文件,避免慢与大。 -
BuildKit:现代 Docker 默认启用的构建引擎,提供更好的缓存/并发/密钥挂载等高级能力。建议在 Dockerfile 顶部注明:
# syntax=docker/dockerfile:1.6
在 Dockerfile 顶部添加这条注释后:
- Docker 会强制使用 BuildKit 引擎(即使未全局启用)
- 可以使用 1.6 版本支持的所有高级语法,如
--mount=type=secret等密钥挂载功能- 构建过程会更加高效,缓存利用更合理
cd && mkdir BuildKit && cd BuildKit
echo "flask" > requirements.txt
echo "my_super_secret_value" > mysecret.txt
tee > app.py << "EOF"
from flask import Flask
app = Flask(__name__)
# 读取 Secret 文件内容
with open("/app/secret.txt", "r") as f:
secret_content = f.read().strip()
@app.route("/")
def hello():
return f"Secret content: {secret_content}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
EOF
tee > dockerfile << "EOF"
# syntax=docker/dockerfile:1.6
FROM python:3.9-slim
# 先创建/app目录
RUN mkdir -p /app
# 可以使用 BuildKit 的高级特性
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret > /app/secret.txt
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
EOF
dockerfile解析
# 指定使用 Dockerfile 1.6 语法,启用 BuildKit 高级特性(如密钥挂载、高效缓存等)
# syntax=docker/dockerfile:1.6
# 基础镜像:使用官方 Python 3.9 精简版,减少镜像体积
FROM python:3.9-slim
# 使用 BuildKit 密钥挂载特性:
# --mount=type=secret,id=mysecret 表示在构建时临时挂载一个名为 mysecret 的密钥
# 密钥内容不会被写入最终镜像,仅在当前 RUN 指令执行期间可见,增强安全性
# 这里将密钥内容写入 /app/secret.txt(实际场景中可能用于配置认证信息等)
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret > /app/secret.txt
# 设置工作目录为 /app,后续指令(如 COPY、RUN 等)将在此目录下执行
WORKDIR /app
# 将本地的 requirements.txt 文件复制到容器的工作目录(/app)
# 单独复制依赖文件可以利用 Docker 缓存:只有依赖变动时才会重新安装依赖
COPY requirements.txt .
# 安装 Python 依赖:
# --no-cache-dir 禁用 pip 缓存,减少镜像体积
# 基于上一步复制的 requirements.txt 安装指定版本的依赖
RUN pip install --no-cache-dir -r requirements.txt
# 将当前目录下的所有文件复制到容器的工作目录(/app)
# 放在依赖安装之后,避免代码变动导致依赖重新安装(利用缓存优化)
COPY . .
# 容器启动时执行的命令:运行 Python 应用程序(app.py)
# 使用 JSON 数组格式可以避免 shell 解析带来的潜在问题
CMD ["python", "app.py"]
2.2.3-启用 BuildKit 并构建镜像
BuildKit 是 Docker 推荐的构建工具,支持 --mount=type=secret 等高级特性,需要显式启用:
# 启用 BuildKit,构建时需要通过 --secret 参数告诉 BuildKit 密钥的来源
DOCKER_BUILDKIT=1 docker build -t my-python-app \
--secret id=mysecret,src=mysecret.txt .

docker run -d -p 5000:5000 my-python-app
# Secret content: my_super_secret_value
curl http://localhost:5000

2.3-常用指令语法速查
FROM:指定基础镜像,可用
AS命名阶段,为多阶段构建做准备。FROM node:20-slim AS buildRUN:在构建期执行命令(产生新层)。可合并命令减少层数,但要权衡可读性与缓存粒度。
COPY vs ADD:
COPY:仅复制文件/目录(推荐默认用)。
ADD:多了解压 tar、远程 URL(常引入不可控行为,慎用)。WORKDIR:设置后续指令的工作目录,无需再
cd。CMD vs ENTRYPOINT:
CMD:容器默认参数,可被docker run ... <args>覆盖。
ENTRYPOINT:固定执行体;与CMD结合:ENTRYPOINT是可执行文件,CMD是默认参数。均优先使用 exec 形式:
["/bin/app", "arg"]ENV:设置环境变量(影响构建与运行时)。
ARG:仅构建期变量(不可在运行时使用)。
EXPOSE:文档性声明端口(不做真正的端口映射)。
USER:指定非 root 运行用户(安全要点!)。
VOLUME:声明数据卷挂载点(通常在运行时用
-v指定更灵活)。LABEL:添加元信息(建议使用 OCI 规范标签,见第 8 节)。
HEALTHCHECK:健康检查(见下文例子)。
SHELL:在 Windows 或特殊环境调整默认 shell。
STOPSIGNAL:自定义停止信号。
ONBUILD:为“父镜像”预注入触发指令(少用,容易踩坑)。
3-构建缓存与分层的最佳实践
# 创建工作目录
cd && mkdir node && cd node
.dockerignore 是一个用于优化 Docker 构建过程的配置文件
3.1-.dockerignore内容解析.git # 忽略 Git 版本控制目录(无需打包到镜像) node_modules # 忽略 Node.js 本地依赖(镜像中会通过 npm install 重新安装) venv # 忽略 Python 虚拟环境(镜像中会重新构建依赖) __pycache__/ # 忽略 Python 编译缓存文件(无实际运行意义) *.log # 忽略所有日志文件(避免冗余) .DS_Store # 忽略 macOS 系统生成的隐藏文件(仅系统相关,与应用无关)
# 在你的项目根目录(与 Dockerfile 同级)创建一个名为 .dockerignore 的文件
tee .dockerignore << "EOF"
.git
node_modules
venv
__pycache__/
*.log
.DS_Store
EOF
3.2-package.json(依赖配置)
# 定义项目依赖(这里用 express 做简单服务)
# JSON 格式 不允许出现任何注释。
tee > package.json << "EOF"
{
"name": "node-docker-demo",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
EOF
3.3-server.js(服务源码)
tee > server.js << "EOF"
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello Docker! 这是 Node.js 服务');
});
app.listen(port, () => {
console.log(`服务运行在 http://localhost:${port}`);
});
EOF
3.4-Dockerfile(多阶段构建)
tee > dockerfile << "EOF"
# syntax=docker/dockerfile:1.6
# 阶段1:安装依赖
FROM node:20-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
# 阶段2:运行应用
FROM node:20-slim AS runner
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["node", "server.js"]
EOF
apt install npm -y && npm install

# 启用 BuildKit(推荐,支持多阶段构建和缓存优化)
DOCKER_BUILDKIT=1 docker build --no-cache -t node-docker-demo .

3.5-验证服务
启动容器,将容器的 3000 端口映射到主机的 3000 端口(便于外部访问)
# -p 主机端口:容器端口;--rm 容器停止后自动删除;-d 后台运行
docker run -p 3000:3000 --rm -d --name my-node-server node-docker-demo
# 输出:服务运行在 http://localhost:3000
docker logs my-node-server
# 输出:Hello Docker! 这是 Node.js 服务
curl http://localhost:3000

3.6-APT 最佳实践:清理列表减少镜像体积
针对 Debian/Ubuntu 系统的 apt-get 命令,核心逻辑是 “安装后立即清理缓存”:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
apt-get update:更新软件包索引(获取最新的软件地址);--no-install-recommends:只安装 “必需依赖”,不安装 “推荐依赖”(减少无用软件);rm -rf /var/lib/apt/lists/*:删除更新后的软件包索引文件(这一步是关键!索引文件仅安装时用,保留会占用几十 MB 空间,清理后镜像更小)。
为什么要合并成一条 RUN?
RUN apt-get update # 生成层1:包含索引文件
RUN apt-get install ... # 生成层2:安装软件,但层1的索引文件还在
RUN rm -rf ... # 生成层3:删除索引,但层1的索引文件仍存在于镜像历史中(镜像体积没减少)
合并成一条 RUN,索引文件会在同一层被删除,最终镜像不会保留冗余文件。
减少层数 vs 可读性:平衡原则
- 合并关键步骤:将 “安装 + 清理”“依赖配置 + 安装” 等强关联、需共享缓存的步骤合并(如 APT 命令、Node.js 依赖安装),减少层数且不浪费空间;
- 不盲目合并:将无关步骤分开(如 “设置工作目录”
WORKDIR、“复制源码”COPY . .),便于后续修改和调试(比如改源码不需要动依赖安装命令)。
4-多阶段构建(瘦身利器)
Docker 多阶段构建的核心思想:分离 “构建环境” 和 “运行环境”
4.1-核心思想:为什么要分 “builder” 和 “runtime” 阶段?
传统单阶段构建会把编译工具、源码、依赖、临时文件全部打包到最终镜像,导致:
- 镜像体积庞大(可能包含 GB 级的编译工具)
- 安全风险(编译工具可能成为攻击面)
- 冗余文件浪费存储空间和传输带宽
多阶段构建通过:
- builder 阶段:使用包含编译工具的镜像(如
golang:alpine、node:slim),完成依赖下载、代码编译、产物生成- runtime 阶段:使用极简基础镜像(如
scratch、nginx:alpine),只复制 builder 阶段的最终产物(如可执行文件、静态页面)最终镜像仅包含运行必需的文件,体积可减少 90% 以上。
4.2-示例 A:Go 语言多阶段构建解析(静态编译到 scratch)
cd && mkdir -p ~/go-app/cmd/server && cd go-app
sudo apt install golang-go -y
tee > dockerfile << "EOF"
# syntax=docker/dockerfile:1.6
FROM golang:1.22-alpine AS build
RUN apk add --no-cache ca-certificates git
ENV GOPROXY=https://goproxy.cn,direct
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o app ./cmd/server
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /src/app /app
EXPOSE 8080
USER 10001:10001
ENTRYPOINT ["/app"]
EOF
go.mod
tee > go.mod << "EOF"
module go-docker-demo
go 1.22
require (
github.com/gorilla/mux v1.8.1
)
EOF
main.go
tee > ./cmd/server/main.go << "EOF"
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go Docker demo!\n")
})
fmt.Println("Server listening on :8080")
if err := http.ListenAndServe(":8080", r); err != nil {
log.Fatalf("Server failed: %v", err)
}
}
EOF
go.sum
# 设置 GOPROXY 环境变量:设置国内的 Go 模块代理
go env -w GOPROXY=https://goproxy.cn,direct
# 再次执行 go mod tidy
go mod tidy

# 阿里云代理
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct# 腾讯云代理
go env -w GOPROXY=https://mirrors.cloud.tencent.com/go/,direct
构建镜像
DOCKER_BUILDKIT=1 docker build --no-cache -t go-app .
docker run -d -p 8080:8080 go-app
# Hello from Go Docker demo!
curl http://localhost:8080
docker rm -f $(docker ps -aq)

4.3-示例 B:Node(构建前端产物,Nginx 提供)
# 创建项目目录
cd && mkdir -p node-nginx-app/src node-nginx-app/public
cd node-nginx-app
dockerfile
# 写入 Dockerfile
tee dockerfile <<'EOF'
# syntax=docker/dockerfile:1.6
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # 例如生成 /app/dist
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:80/ || exit 1
EOF
package.json
# 写入 package.json
tee package.json <<'EOF'
{
"name": "node-nginx-demo",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "mkdir -p dist && cp -r public/* dist/ && echo 'console.log(\"App built successfully!\")' > dist/main.js"
},
"dependencies": {}
}
EOF
public/index.html
# 写入示例 HTML
tee public/index.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Node + Nginx Demo</title>
</head>
<body>
<h1>Hello from Node + Nginx Docker demo!</h1>
<script src="main.js"></script>
</body>
</html>
EOF
src/index.js
# 写入 JS 源文件(可选)
tee src/index.js <<'EOF'
console.log("Frontend source code goes here.");
EOF
# 写入 Nginx 配置(可选优化)
tee nginx.conf <<'EOF'
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
# 静态资源缓存优化
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires 30d;
access_log off;
}
}
EOF
构建并运行容器
npm install && ls
DOCKER_BUILDKIT=1 docker build -t node-nginx-demo .
docker run -d -p 8080:80 node-nginx-demo
curl http://localhost:8080
| 文件 | 作用 |
|---|---|
Dockerfile | 多阶段构建:Node 构建 → Nginx 托管 |
package.json | 定义构建脚本(模拟前端项目) |
public/ | 前端静态资源目录 |
nginx.conf | 自定义 Nginx 配置(可选) |
dist/ | 构建输出(由 npm run build 生成) |
5-语言生态的依赖缓存技巧(Python + Node 两种)
# 创建示例根目录
cd && mkdir -p lang-cache-demo && cd lang-cache-demo
5.1-Python 缓存实例
mkdir -p python-app
cd python-app
Dockerfile
# 写入 Dockerfile
tee dockerfile <<'EOF'
# syntax=docker/dockerfile:1.6
FROM python:3.12-slim AS base
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# 复制依赖文件(单独COPY以命中缓存)
COPY requirements.txt .
# 使用 BuildKit 缓存 pip 缓存目录
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-cache-dir -r requirements.txt
COPY . .
USER 1000
EXPOSE 8000
CMD ["python", "app.py"]
EOF
requirements.txt
# 写入 requirements.txt
tee requirements.txt <<'EOF'
flask==3.0.3
EOF
app.py
# 写入 app.py
tee app.py <<'EOF'
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Python Docker cache demo!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
EOF
构建 Python 示例
DOCKER_BUILDKIT=1 docker build -t python-cache-demo .
docker run -d -p 8000:80 python-cache-demo

# Hello from Python Docker cache demo!
curl http://localhost:8000

5.2-Node 缓存示例
cd ~ && mkdir -p node-app
cd node-app
Dockerfile
# 写入 Dockerfile
tee Dockerfile <<'EOF'
# syntax=docker/dockerfile:1.6
FROM node:20-slim AS base
WORKDIR /app
# 复制依赖说明(利用缓存)
COPY package*.json ./
# 使用 BuildKit 缓存 npm 缓存目录
RUN --mount=type=cache,target=/root/.npm \
npm ci
# 复制源码
COPY . .
USER 1000
EXPOSE 3000
CMD ["node", "server.js"]
EOF
package.json
# 写入 package.json
tee package.json <<'EOF'
{
"name": "node-cache-demo",
"version": "1.0.0",
"description": "Demo for Docker layer caching with npm",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
EOF
server.js
# 写入 server.js
tee server.js <<'EOF'
const express = require("express");
const app = express();
const PORT = 3000;
app.get("/", (req, res) => {
res.send("Hello from Node Docker cache demo!");
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
EOF
构建 Node 示例
npm install
DOCKER_BUILDKIT=1 docker build -t node-cache-demo .
docker run -d -p 3000:3000 node-cache-demo

# Hello from Node Docker cache demo!
curl http://localhost:3000
docker rm -f $(docker ps -aq)

| 语言 | 缓存挂载路径 | 命令 | 特点 |
|---|---|---|---|
| Python | /root/.cache/pip | pip install | 缓存包轮次,命中率高 |
| Node | /root/.npm | npm ci | 精确锁版本、重复构建一致性好 |
6-安全最佳实践(务必养成习惯)
6.1-项目结构
node-security-demo/
├── Dockerfile # 集成所有安全最佳实践的构建配置
├── .dockerignore # 排除冗余/敏感文件,减少构建上下文
├── package.json # 项目依赖(生产依赖仅1个)
├── package-lock.json # 依赖锁文件(锁定版本,确保可重现构建)
└── .npmrc.secret # 私有 npm 源密钥(敏感文件,不提交Git)
cd ~ && mkdir node-security-demo && cd node-security-demo
6.2-各文件内容与安全实践说明
package.json(仅保留生产依赖,减少攻击面)
tee > package.json << "EOF"
{
"name": "node-security-demo",
"version": "1.0.0",
"main": "server.js",
"dependencies": {
"express": "^4.19.2"
},
"scripts": {
"start": "node server.js"
}
}
EOF
package-lock.json(执行 npm install 自动生成,锁定依赖版本)
执行以下命令生成锁文件(确保依赖版本不可变,避免 “依赖漂移”)
# 如果版本低可以更新
# 移除旧版 node/npm
sudo apt remove -y nodejs npm
# 使用 NodeSource 安装 Node.js 20.x LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
npm install express # 安装后自动生成 package-lock.json
敏感密钥文件:.npmrc.secret(BuildKit 安全挂载,不进镜像)
若项目需从私有 npm 源安装依赖(如公司内部包),需配置
.npmrc,但密钥不能写进 Dockerfile 或提交 Git
# .npmrc.secret(敏感文件,仅本地存在,不提交Git)
tee > .npmrc.secret << "EOF"
registry=https://registry.npm.taobao.org/ # 示例:公开源(实际可替换为私有源)
//registry.npm.taobao.org/:_authToken=your-private-token # 私有源密钥(示例值,需替换)
EOF
构建优化文件:.dockerignore(减少上下文,排除敏感文件)
# 在你的项目根目录(与 Dockerfile 同级)创建一个名为 .dockerignore 的文件
tee .dockerignore << "EOF"
# 排除Git相关(无运行意义)
.git
.gitignore
# 排除本地依赖(镜像中通过 npm ci 重新安装,避免冗余)
node_modules
# 排除敏感文件(密钥文件不进入构建上下文)
.npmrc.secret
.env
.env.local
# 排除编译缓存/日志(避免缓存失效和冗余)
__pycache__/
*.log
.DS_Store
EOF
核心安全配置:Dockerfile(集成所有最佳实践)
tee > dockerfile << "EOF"
# syntax=docker/dockerfile:1.6
FROM node:20.15.0-slim AS builder
RUN addgroup --system appgroup && adduser --system --ingroup appgroup --home /home/appuser appuser
USER appuser
WORKDIR /app
ENV HOME=/home/appuser
COPY --chown=appuser:appgroup package*.json ./
# 只写 id 和 target,避免与构建镜像的参数重叠出现报错
RUN --mount=type=secret,id=npmrc,target=/home/appuser/.npmrc \
npm ci --omit=dev
COPY --chown=appuser:appgroup . .
# ---------- Runtime ----------
FROM node:20.15.0-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup --home /home/appuser
USER appuser
WORKDIR /app
ENV HOME=/home/appuser
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package*.json ./
COPY --from=builder --chown=appuser:appgroup /app/server.js ./
EXPOSE 3000
CMD ["node", "server.js"]
EOF
项目源码:server.js(简单 HTTP 服务,用于验证)
tee > server.js << "EOF"
// server.js(仅含运行必需功能,无多余代码)
const express = require('express');
const app = express();
const port = 3000;
// 仅暴露1个健康检查接口(减少攻击面)
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
// 启动服务(绑定0.0.0.0,允许容器内访问)
app.listen(port, '0.0.0.0', () => {
console.log(`Server running on port ${port} (non-root user)`);
});
EOF
6.3-验证安全实践
构建镜像(启用 BuildKit,安全挂载密钥)
# 关键:--secret 指定密钥文件,id需与Dockerfile中一致
DOCKER_BUILDKIT=1 docker build -t node-security-demo \
--secret id=npmrc,src=.npmrc.secret .

- 构建过程中,
.npmrc.secret仅临时挂载到容器,不会写入任何镜像层(用docker historyimageID/name可验证);- 若依赖未变,
npm ci步骤会复用缓存(查看日志有CACHED标识)。
运行容器(验证非 root 用户与功能)
# 启动容器:不暴露多余端口,仅映射3000
docker run -d -p 3000:3000 --name secure-node-app node-security-demo
验证 1:非 root 用户运行
进入容器查看当前用户(distroless 镜像无 shell,需用 exec 执行命令):
# 查看容器内用户ID(nonroot用户ID通常为65532)
docker exec secure-node-app id
# 输出示例:uid=65532(nonroot) gid=65532(nonroot) groups=65532(nonroot)

验证 2:服务正常运行
访问健康检查接口,确认服务可用:
curl http://localhost:3000/health
# 输出示例:{"status":"ok","timestamp":"2024-06-01T12:00:00.000Z"}

验证 3:镜像体积(最小化)
查看镜像大小(distroless 基础镜像 + express 依赖,总大小约 80MB,比完整版小 80%):
docker images node-security-demo
# 输出示例:REPOSITORY TAG SIZE
# node-security-demo latest 82.3MB

6.4-漏洞扫描(用 Trivy 检测镜像风险)
安装 Trivy(轻量级漏洞扫描工具)后,扫描镜像是否存在高危漏洞:
# 1. 安装依赖
sudo apt-get update -y
sudo apt-get install -y wget apt-transport-https gnupg lsb-release
# 1. 下载公钥并强制指定为ASCII-armored格式(GPG默认解析格式)
curl -sfL https://mirrors.ustc.edu.cn/aquasecurity/trivy-repo/deb/public.key > /tmp/trivy.pub
# 2. 用GPG明确解析ASCII-armored格式的公钥
sudo gpg --dearmor --yes --input /tmp/trivy.pub --output /usr/share/keyrings/trivy.gpg
# 3. 验证文件是否生成(应显示文件大小)
ls -lh /usr/share/keyrings/trivy.gpg
# 添加USTC镜像源(关联已导入的公钥)
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://mirrors.ustc.edu.cn/aquasecurity/trivy-repo/deb stable main" | sudo tee /etc/apt/sources.list.d/trivy.list
# 更新并安装
sudo apt update && sudo apt install -y trivy
- 若扫描出高危漏洞,需更新基础镜像(如升级
node版本)或依赖(修改package.json后重新生成package-lock.json)。
清理容器(避免资源占用)
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
| 安全最佳实践 | 本案例中的实现方式 |
|---|---|
| 非 root 运行 | 构建阶段用 appuser,运行阶段用 distroless 自带的 nonroot 用户,避免 root 权限 |
| 最小化基础镜像 | 构建用 node:slim,运行用 distroless(仅含 Node.js 运行时,无 shell / 包管理工具) |
| 锁定版本 | 基础镜像用 @sha256:xxx,依赖用 package-lock.json,Dockerfile 语法锁定 1.6 |
| 减少攻击面 | 仅安装生产依赖(--only=production),仅暴露 1 个健康检查接口,无多余工具 |
| 秘密管理 | BuildKit --mount=type=secret 挂载 .npmrc.secret,不写入镜像层 |
| 漏洞扫描 | 用 Trivy 扫描镜像,及时修复高危漏洞 |
5136

被折叠的 条评论
为什么被折叠?



