故城瞎折腾系列第一期【你要不要动手封装个前端Docker容器玩一玩】
1. 背景
- 一年多前我分享的一片前端Docker容器化入门反响不错,被 Docker 中文社区转载
- 很多人估计看完都没有找到下手点,所以我将我常用的Docker容器分享出来,希望能帮助大家使用容器化管理前端发布
- 故城已经利用这个Docker容器开发了很多前端SPA项目了
文章链接:前端应用 Docker 容器化最佳实践
文章链接:30分钟手把手带你入门前端容器化(Docker)
2. 导语
- 由于故城更倾向于做一些篇幅内容较短的技术文章,所以暂时不分析实现原理,也好让自己更好的坚持下去
- 写过文章的同学都知道,字数写多了就很容易犯强迫症,就很容易纠结文章内容,反复读写多遍,导致很难坚持下去,故城很多年以前也写出过一些阅读量不错的技术文章,由于篇幅很长,写作量大,所以未能坚持下来
- 此篇文章只分享 Docker 容器如何使用,感兴趣自己可以进一步封装
- 废话不多说先上仓库:Nginx-Runner,对你有帮助请 star、fork 支持一下
以下是我的【故城瞎折腾系列】文章
第一篇:故城瞎折腾系列第一期【你要不要动手封装个前端Docker容器玩一玩】
第二篇:故城瞎折腾系列第二期【都2024年了,你还在手动部署前端项目吗】
第三篇:故城瞎折腾系列第三期【你看我这样用Nginx部署前端Docker项目,姿势对不对】
3. Docker容器(Nginx-Runner)包含的功能
- 封装了前端 web 应用基本的 nginx 配置
- 利用 docker env 提供了前端环境变量注入的功能
4. Nginx-Runner如何使用
4.1. 在项目根目录新建Dockerfile
pnpm缓存,使用以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# syntax = docker/dockerfile:experimental
FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} node:20-buster AS builder
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /src
COPY ./ /src
RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
--mount=type=cache,target=/pnpm/store,id=pnpm_cache \
pnpm install
RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
pnpm run build
FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} ghcr.io/rookie-luochao/nginx-runner:latest
COPY --from=builder /src/dist /app
无缓存,使用以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# syntax = docker/dockerfile:experimental
FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} node:20-buster AS builder
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /src
COPY ./ ./
# RUN两次方便观察install和build, 也可以用pnpm cache and locked
RUN pnpm install
RUN npm run build
FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} ghcr.io/rookie-luochao/nginx-runner:latest
COPY --from=builder /src/dist /app
4.2. 使用 github-action 构建 docker 镜像
项目根目录执行:新建.github文件夹 => 在.github文件夹下面新建workflows文件夹 => 新建 docker-image-ci.yml文件 然后贴入一下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
name: Docker Image CI
on:
push:
tags:
- v*
# 这个选项可以使你手动在 Action tab 页面触发工作流
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: get version
id: vars
run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\/v/}
- uses: actions/checkout@v4
- name: set up QEMU
uses: docker/setup-qemu-action@v3
- name: set up docker buildx
uses: docker/setup-buildx-action@v3
- name: login ghrc hub
uses: docker/login-action@v3
with:
registry: ghcr.io
username: $
password: $
- name: build and push
uses: docker/build-push-action@v5
with:
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/$/webapp:$
ghcr.io/$/webapp:latest
4.3. 配置 github-action Token,并触发 docker 镜像构建
- 将项目推到 github 仓库
- 需要申请一个 github Token(建议权限全部勾选上)
- 点击仓库 Tab 页面的 Settings
- 点击左侧边栏:Secrets and variables
- 点击 Actions
- 点击 New repository secrets 添加 github Token
- 添加 GHCR_TOKEN 对应的 TOKEN,github-action脚本里面会用到
- 新建仓库 release 去触发 docker 镜像构建
4.4. 如何运行 Docker 镜像
这里以故城的 openapi-ui 项目为例子
- 拉取 docker 镜像到本地,
docker pull ghcr.io/rookie-luochao/openapi-ui:latest
- 执行 docker 镜像,
docker run -d -p 80:80 -e APP_CONFIG=env=zh,appNameZH=简洁美观的接口文档 ghcr.io/rookie-luochao/openapi-ui:latest
1
2
3
4
5
# pull Docker image
docker pull ghcr.io/rookie-luochao/openapi-ui:latest
# start container, nginx reverse proxy custom port, for example: docker run -d -p 8081:80 ghcr.io/rookie-luochao/openapi-ui:latest
docker run -d -p 80:80 -e APP_CONFIG=env=zh,appNameZH=简洁美观的接口文档 ghcr.io/rookie-luochao/openapi-ui:latest
5. Nginx-Runner如何注入环境变量,兼容 .env 文件设置的环境变量
5.1. 改造index.html文件,加入环境变量占位符
如下代码,包含meta标签:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo_mini.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="keywords"
content="openapi-ui, swagger-ui, openapi, swagger, openapi3, openapi31, api-documentation, openapi-specification"
/>
<meta name="description" content="openapi ui document/specification, swagger ui document/specification" />
<meta name="env" content="__ENV__" />
<meta name="app_config" content="__APP_CONFIG__" />
<title>openAPI UI</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
5.2. 增加获取环境变量工具函数
获取环境变量工具函数的完整代码 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import appConfig, { IConfig } from "@/config";
export function getConfig(): IConfig {
const mateEnv = import.meta.env;
const defaultAppConfig = {
appName: mateEnv?.VITE_appName || "",
appNameZH: mateEnv?.VITE_appNameZH || "",
baseURL: mateEnv?.VITE_baseURL || "",
version: mateEnv?.VITE_version || "",
env: mateEnv?.VITE_env || "",
};
// dev mode get env var by src/config.ts file, prod mode get env var by mate, write the mate tag of HTML through docker arg var
// mate tag name is:app_config, content format is:appName=webapp,baseURL=https://api.com,env=,version=
if (import.meta.env.DEV) {
return appConfig;
} else {
const appConfigStr = getMeta("app_config");
if (!appConfigStr) return defaultAppConfig;
return parseEnvVar(appConfigStr);
}
}
function getMeta(metaName: string) {
const metas = document.getElementsByTagName("meta");
for (let i = 0; i < metas.length; i++) {
if (metas[i].getAttribute("name") === metaName) {
return metas[i].getAttribute("content");
}
}
return "";
}
function parseEnvVar(envVarURL: string) {
const arrs = envVarURL.split(",");
return arrs.reduce((pre, item) => {
const keyValues = item.split("=");
return {
...pre,
[keyValues[0]]: keyValues[1],
};
}, {} as IConfig);
}
完整例子可以直接使用故城的 react + docker模板
6. 结语
- Nginx-Runner的基本功能和使用
- Nginx-Runner如何注入前端环境变量
- 参考Docker入门
- 看都看完了,还不动手操作一波
本文由作者按照 CC BY 4.0 进行授权