# 姜十三论坛 — 多阶段构建:Node 前端(Vike SSR)+ Go API,双服务共用镜像 # 不使用 # syntax=docker/dockerfile:1,避免构建前额外拉取 docker.io/docker/dockerfile # # 国内网络:默认经 DaoCloud 拉取基础镜像,npm/go 走国内代理 # 海外或已配置 Docker registry-mirrors 时可传空前缀: # docker build --build-arg IMAGE_PREFIX= ... # # 运行形态(docker compose 双容器,同一镜像两种角色): # api —— Go 二进制,纯 API(:3001 内部,/data 数据卷) # web —— Node 渲染服务器(对外 :3000:SSR + 静态资源 + API 反代) # 镜像默认入口为 api 角色;web 角色在 compose 中以 node 用户覆盖 entrypoint。 ARG IMAGE_PREFIX=docker.m.daocloud.io/library/ ARG VERSION=dev # ── Stage 1: 前端构建(Vike → dist/client 静态资源 + dist/server SSR 入口)── FROM ${IMAGE_PREFIX}node:24-bookworm-slim AS frontend WORKDIR /src/frontend COPY frontend/package.json frontend/package-lock.json ./ RUN npm config set registry https://registry.npmmirror.com \ && npm ci COPY frontend/ ./ RUN npm run build # ── Stage 2: Go 编译(纯 Go SQLite,CGO_ENABLED=0;仅 API,无内嵌前端)───── FROM ${IMAGE_PREFIX}golang:1.26-bookworm AS builder ARG VERSION ENV GOPROXY=https://goproxy.cn,direct WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -trimpath \ -ldflags "-s -w -X main.version=${VERSION}" \ -o /out/jiang13 ./cmd/jiang13 # ── Stage 3: 运行镜像(Node 运行时承载 SSR 渲染与静态资源,附带 Go API 二进制) FROM ${IMAGE_PREFIX}node:24-bookworm-slim RUN apt-get update \ && apt-get install -y --no-install-recommends gosu ca-certificates tzdata \ && rm -rf /var/lib/apt/lists/* ENV TZ=Asia/Shanghai \ NODE_ENV=production WORKDIR /app COPY --from=builder /out/jiang13 /app/jiang13 COPY --from=frontend /src/frontend /app/frontend COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /app/jiang13 /docker-entrypoint.sh # 以 root 启动,entrypoint 修正 /data 卷权限后 gosu 降权为 uid 1000(node 用户) VOLUME ["/data"] EXPOSE 3000 3001 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:3001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["--work-path", "/app", "--data", "/data", "--port", "3001"]