Frontend Development 22 min read

How to Containerize a React Frontend with Docker, GitHub Actions, and Kubernetes

This guide explains how to containerize a React front‑end application using Docker, set up CI/CD pipelines with GitHub Actions, optimize builds with pnpm caching and buildx for multi‑architecture images, inject environment variables, and deploy the resulting container to Kubernetes.

Ops Development Stories
Ops Development Stories
Ops Development Stories
How to Containerize a React Frontend with Docker, GitHub Actions, and Kubernetes

1. Introduction

Front‑end containerization packages a front‑end application into a container, enabling fast, efficient deployment across environments.

2. Background

With the rise of front‑back separation, front‑end projects have grown in complexity, differing Node.js versions, and lack a single artifact for deployment. Containers simplify deployment, environment variable injection, version rollback, multi‑architecture builds, CI/CD, and DevOps.

3. Using GitHub Actions for CI/CD

GitHub Actions can automate npm publishing. The steps are:

Create

.github/workflows/ci.yml

in the project root.

Obtain an npm token.

Paste the workflow code.

Push to the

master

branch to trigger the pipeline.

<code>name: CI
on:
  push:
    branches:
      - master
jobs:
  build:
    # specify OS
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16.x'
          registry-url: 'https://registry.npmjs.org'
      - name: Cache
        id: cache-dependencies
        uses: actions/cache@v3
        with:
          path: |
            **/node_modules
          key: ${{runner.OS}}-${{hashFiles('**/pnpm-lock.yaml')}}
      - name: Install pnpm
        run: npm install -g [email protected]
      - name: Installing Dependencies
        if: steps.cache-dependencies.outputs.cache-hit != 'true'
        run: pnpm install
      - name: Running Build
        run: pnpm run build
      - name: Running Test
        run: pnpm run test-unit
      - name: Running Publish
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
</code>

4. Building a Front‑end Image with Docker

4.1 Install Docker

Install Docker and verify the version (preferably with buildx support).

<code>docker -v
Docker version 24.0.2, build cb74dfc
</code>

4.2 Write a Dockerfile

A typical React project needs

package.json

,

npm install

, and

npm run build

. The Dockerfile uses a multi‑stage build: first a Node builder, then an Nginx image serving the built files.

<code>FROM node:17-buster as builder

WORKDIR /src
COPY ./ /src

RUN npm install -g pnpm \
    && pnpm install \
    && pnpm build

FROM nginx:alpine-slim

RUN mkdir -p /usr/share/nginx/front/dist \
    && rm -rf /etc/nginx/nginx.conf

COPY --from=builder /src/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /src/dist /usr/share/nginx/front/dist

EXPOSE 80
</code>

Build and run the image:

<code>docker buildx build -t webapp-demo:v1 .

docker run -d -p 80:80 webapp-demo:v1
</code>

4.3 pnpm Cache in Docker

Using multi‑stage builds and cache mounts reduces intermediate layers when

package.json

changes. Example Dockerfile with pnpm cache mounts is provided.

<code>FROM node:20-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app
COPY . /app

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build

FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 8000
CMD ["pnpm", "start"]
</code>

4.4 Buildx for Multi‑Architecture Images

Docker

buildx

enables building images for different CPU architectures. Create a builder that supports multiple platforms, optionally using a custom buildkit image for faster pulls in China.

<code># Create a builder for domestic environment
docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master

# List builders
docker buildx ls

# Build and push multi‑arch image
docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t myusername/hello . --push
</code>
If you have a private image accelerator, you can build your own buildkit image based on https://github.com/docker-practice/buildx.

5. Injecting Front‑end Environment Variables via Containers

Common variables include API baseURL, appName, and env.

Micro‑frontend scenarios may require many URLs.

Traditional approaches use domain checks or framework flags.

Containerization allows injecting variables directly into HTML meta tags, which the front‑end reads at runtime.

In monorepos, the build target can be selected via container variables.

Production setups may generate a

default.yml

that K8s injects into the container.

Example Dockerfile for a production environment (variables are assumed to be already injected):

<code>FROM --platform=${BUILDPLATFORM} hub-dev.rockontrol.com/rk-infrav2/docker.io/library/node:17-bullseye as builder

WORKDIR /src
COPY ./ ./

ARG APP
ARG ENV
ARG PROJECT_GROUP
ARG PROJECT_NAME
ARG PROJECT_VERSION
ARG YARN_NPM_REGISTRY_SERVER

RUN npm install -g --registry=${YARN_NPM_REGISTRY_SERVER} pnpm
RUN pnpm --registry=${YARN_NPM_REGISTRY_SERVER} install

RUN PROJECT_GROUP=${PROJECT_GROUP} PROJECT_VERSION=${PROJECT_VERSION} \
    npx devkit build --prod ${APP} ${ENV}

FROM hub-dev.rockontrol.com/rk-infrav2/ghcr.io/zboyco/webrunner:0.0.7

ARG PROJECT_NAME
COPY --from=builder /src/public/${PROJECT_NAME} /app
</code>

Shell script that writes environment variables into Nginx configuration and HTML meta tags:

<code>#!/bin/sh
app_config="${APP_CONFIG}"
ext_config=""

for var in $(env | cut -d= -f1); do
  if [ "$(echo "$var" | grep '^APP_CONFIG__')" ]; then
    trimmed_var=$(echo "$var" | sed 's/^APP_CONFIG__//')
    value=$(eval echo "\${$var}")
    app_config="${app_config},${trimmed_var}=${value}"
  fi
done

export app_config=$(echo "$app_config" | sed 's/^,//')
IFS=","; set -- $app_config
for config in "$@"; do
  IFS="="
  set -- $config
  ext_config="${ext_config}        sub_filter '__${1}__' '${2}';\n"
done

sed "s@__EXTENT_CONFIG__@${ext_config}@g" /etc/nginx/conf.d/conf-base.template > /etc/nginx/conf.d/conf.template
envsubst '${PROJECT_VERSION} ${ENV} ${app_config}' < /etc/nginx/conf.d/conf.template > /etc/nginx/conf.d/default.conf
nginx -g 'daemon off;'
</code>

Front‑end code to read the injected meta tag:

<code>import appConfig from "../../config";

export function getConfig() {
  const defaultAppConfig = {
    appName: "",
    version: "",
    env: "",
    baseURL: "",
  };
  if (import.meta.env.DEV) {
    return appConfig;
  } else {
    const appConfigStr = getMeta("app_config");
    if (!appConfigStr) return defaultAppConfig;
    return parseEnvVar(appConfigStr);
  }
}
function getMeta(metaName) {
  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) {
  const arrs = envVarURL.split(",");
  return arrs.reduce((pre, item) => {
    const keyValues = item.split("=");
    return {
      ...pre,
      [keyValues[0]]: keyValues[1],
    };
  }, {});
}
</code>

6. Deploying the Front‑end to Kubernetes

Kubernetes automates container orchestration. Create a Deployment and a Service to expose the front‑end.

<code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend-app
  template:
    metadata:
      labels:
        app: frontend-app
    spec:
      containers:
        - name: frontend-app
          image: my-frontend-app:latest
          ports:
            - containerPort: 3000
</code>
<code>apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer
</code>

Deploy with:

<code>kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
</code>

7. React Project Architecture Overview

Core Technologies

Build: Vite

Package manager: pnpm

Language: TypeScript

Framework: React

Routing: react-router

UI library: antd

CSS‑in‑JS: emotion

State: zustand

API generation: OpenAPI

HTTP client: axios

Data fetching: react-query

Hooks: ahooks

Error boundary: react-error-boundary

Logging: sentry‑javascript (not integrated)

Transpilation: Babel

Linter: ESLint

TS lint: typescript‑eslint

Formatter: Prettier

Git hooks: husky

Commit lint: commitlint

OpenAPI‑generated API Functions

<code>// src/core/openapi/index.ts
generateService({
  schemaPath: `${appConfig.baseURL}/${urlPath}`,
  serversPath: "./src",
  requestImportStatement: `/// <reference types="./typings.d.ts" />\nimport request from "@request"`,
  namespace: "Api",
});
</code>

Using react‑query for Requests

<code>// Example request
export async function HelloGet(params: Api.HelloGetParams, options?: { [key: string]: any }) {
  return request<Api.HelloResp>('/gin-demo-server/api/v1/hello', {
    method: 'GET',
    params: { ...params },
    ...(options || {}),
  });
}

// React component
const { data, isLoading } = useQuery({
  queryKey: ["hello", name],
  queryFn: () => HelloGet({ name }),
});
</code>

8. CLI Repository

Code repository: https://github.com/rookie-luochao/create-vite-app-cli

9. Conclusion

Introduced basic GitHub Action configuration for front‑end npm workflows.

Demonstrated Dockerfile creation and pnpm optimization, including multi‑arch builds with buildx.

Explained usage of environment variables in production containers.

Presented a comprehensive React project technical stack.

frontendDockerci/cdReActKubernetescontainerizationbuildx
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.