From 9129a5ce23076b88fdfc8df1a3e710e6ee990638 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 08:23:29 +0000 Subject: [PATCH 1/4] Initial plan From eb464a698cbe0cb06676fb000da96958078971f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 08:26:38 +0000 Subject: [PATCH 2/4] feat: add docker image build and ghcr release --- .dockerignore | 6 ++++ .github/workflows/docker.yaml | 28 ++++++++++++++++++ .github/workflows/ghcr-release.yaml | 45 +++++++++++++++++++++++++++++ Makefile | 32 ++++++++++++++++++++ README.md | 29 +++++++++++++++++++ docker/Dockerfile | 21 ++++++++++++++ docker/compose.yaml | 11 ++----- 7 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker.yaml create mode 100644 .github/workflows/ghcr-release.yaml create mode 100644 docker/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..34622b1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +test-results +playwright-report +.git +.github diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000..76e4dd8 --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,28 @@ +name: docker + +on: + push: + branches: + - "main" + - "feature/**" + pull_request: + branches: + - "main" + +permissions: + contents: read + +jobs: + docker: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # to retrieve git tags + - name: Add to PATH + shell: bash + run: echo "/home/runner/bin" >> "$GITHUB_PATH" + - name: Run CI tests for Docker + shell: bash + run: make ci-test-docker TOOLS_DIR="/home/runner/bin" diff --git a/.github/workflows/ghcr-release.yaml b/.github/workflows/ghcr-release.yaml new file mode 100644 index 0000000..5d32f63 --- /dev/null +++ b/.github/workflows/ghcr-release.yaml @@ -0,0 +1,45 @@ +name: ghcr + +on: + push: + tags: + - "v*" + +permissions: + packages: write + contents: read + +env: + IMAGE_NAME: template-typescript-react + +jobs: + ghcr: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/metadata-action@v6 + id: meta + with: + images: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{version}} + type=raw,value=latest + - uses: docker/build-push-action@v7 + with: + context: . + file: docker/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + build-args: | + GIT_REVISION=${{ github.sha }} + GIT_TAG=${{ github.ref_name }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Makefile b/Makefile index 1e797a9..29f4e1c 100644 --- a/Makefile +++ b/Makefile @@ -59,3 +59,35 @@ preview: ## preview production build .PHONY: update update: ## update dependencies pnpm update --latest + +# --- +# Docker +# --- +DOCKER_REPO_NAME ?= ks6088ts +DOCKER_IMAGE_NAME ?= template-typescript-react +DOCKER_FILE ?= docker/Dockerfile +TRIVY_VERSION ?= 0.69.3 + +.PHONY: docker-build +docker-build: ## build Docker image + docker build -f $(DOCKER_FILE) \ + -t $(DOCKER_REPO_NAME)/$(DOCKER_IMAGE_NAME):$(GIT_TAG) \ + --build-arg GIT_REVISION=$(GIT_REVISION) \ + --build-arg GIT_TAG=$(GIT_TAG) \ + . + +.PHONY: docker-run +docker-run: ## run Docker container (serves on http://localhost:8080) + docker run --rm -p 8080:80 $(DOCKER_REPO_NAME)/$(DOCKER_IMAGE_NAME):$(GIT_TAG) + +.PHONY: docker-lint +docker-lint: ## lint Dockerfile + docker run --rm -i hadolint/hadolint < $(DOCKER_FILE) + +.PHONY: docker-scan +docker-scan: ## scan Docker image + @which trivy || curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b $(TOOLS_DIR) v$(TRIVY_VERSION) + trivy image $(DOCKER_REPO_NAME)/$(DOCKER_IMAGE_NAME):$(GIT_TAG) + +.PHONY: ci-test-docker +ci-test-docker: docker-lint docker-build docker-scan ## run CI test for Docker (build only, no run) diff --git a/README.md b/README.md index 7ec4456..1e99c90 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![test](https://github.com/ks6088ts/template-typescript-react/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/test.yaml?query=branch%3Amain) [![e2e-test](https://github.com/ks6088ts/template-typescript-react/actions/workflows/e2e-test.yaml/badge.svg?branch=main)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/e2e-test.yaml?query=branch%3Amain) +[![docker](https://github.com/ks6088ts/template-typescript-react/actions/workflows/docker.yaml/badge.svg?branch=main)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/docker.yaml?query=branch%3Amain) +[![ghcr](https://github.com/ks6088ts/template-typescript-react/actions/workflows/ghcr-release.yaml/badge.svg)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/ghcr-release.yaml) [![github-pages](https://github.com/ks6088ts/template-typescript-react/actions/workflows/github-pages.yaml/badge.svg?branch=main)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/github-pages.yaml?query=branch%3Amain) [![release](https://github.com/ks6088ts/template-typescript-react/actions/workflows/release.yaml/badge.svg)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/release.yaml) [![GitHub release](https://img.shields.io/github/v/release/ks6088ts/template-typescript-react?logo=github&label=release)](https://github.com/ks6088ts/template-typescript-react/releases/latest) @@ -72,6 +74,32 @@ pnpm format A `Makefile` wraps common workflows — run `make help` to list targets (e.g. `make ci-test`, `make e2e`, `make ci-test-e2e`). +## Docker + +Build and run the production nginx image locally: + +```bash +# Build the production image +make docker-build + +# Run the container (http://localhost:8080) +make docker-run +``` + +You can also start the same image with Docker Compose: + +```bash +docker compose -f docker/compose.yaml up --build web +``` + +For Docker-focused CI checks, run: + +```bash +make ci-test-docker +``` + +Pushing a `v*` tag keeps the existing release asset flow and also publishes a multi-arch image to `ghcr.io/ks6088ts/template-typescript-react`. + ## Testing Two E2E suites run headless by default — Vitest browser mode and Playwright: @@ -102,6 +130,7 @@ src/ # React app source __tests__/e2e/ # Vitest browser E2E tests playwright/ # Playwright smoke tests docker/ # OTel Collector + Grafana LGTM stack + Dockerfile # Production nginx image for built SPA assets docs/ # Detailed documentation .github/workflows/ # CI/CD pipelines ``` diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..2be416a --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +# ---- build stage ---- +FROM node:24.10.0-alpine AS build +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@10.33.0 --activate +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile +COPY . . +RUN pnpm build + +# ---- runtime stage ---- +FROM nginx:alpine AS runtime +ARG GIT_REVISION="0000000" +ARG GIT_TAG="x.x.x" +LABEL org.opencontainers.image.revision="${GIT_REVISION}" \ + org.opencontainers.image.version="${GIT_TAG}" \ + org.opencontainers.image.source="https://github.com/ks6088ts/template-typescript-react" +COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/docker/compose.yaml b/docker/compose.yaml index e1f801f..04a4069 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -1,15 +1,10 @@ services: web: - # Serve the pre-built assets in ../dist with nginx. - # Build them first: `pnpm build` (or `make build`), then `docker compose up web`. - image: nginx:alpine + build: + context: .. + dockerfile: docker/Dockerfile ports: - "8080:80" - volumes: - # Production build output from Vite (read-only). - - ../dist:/usr/share/nginx/html:ro - # SPA-friendly nginx config (falls back to index.html). - - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro otel-collector: image: otel/opentelemetry-collector-contrib:latest From 4ec5cac4cf327c92f7a3ae1718fbbfde473bbec3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 08:27:15 +0000 Subject: [PATCH 3/4] docs: fix dockerfile indentation in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e99c90..6971164 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ src/ # React app source __tests__/e2e/ # Vitest browser E2E tests playwright/ # Playwright smoke tests docker/ # OTel Collector + Grafana LGTM stack - Dockerfile # Production nginx image for built SPA assets + Dockerfile # Production nginx image for built SPA assets docs/ # Detailed documentation .github/workflows/ # CI/CD pipelines ``` From 7316a9cb2c13a0f288898593c3ec8bdf6b05e2ac Mon Sep 17 00:00:00 2001 From: ks6088ts Date: Sat, 13 Jun 2026 17:38:21 +0900 Subject: [PATCH 4/4] feat: add docker-release workflow and update README with Docker Hub integration --- .github/workflows/docker-release.yaml | 49 +++++++++++++++++++++++++++ README.md | 21 +++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docker-release.yaml diff --git a/.github/workflows/docker-release.yaml b/.github/workflows/docker-release.yaml new file mode 100644 index 0000000..1233f3f --- /dev/null +++ b/.github/workflows/docker-release.yaml @@ -0,0 +1,49 @@ +name: docker-release +on: + push: + tags: + - "v*" +permissions: + contents: read +env: + IMAGE_NAME: template-typescript-react +jobs: + release: + runs-on: "ubuntu-latest" + timeout-minutes: 10 + steps: + - name: Check out the repo + uses: actions/checkout@v6 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - name: Log in to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + # https://github.com/docker/build-push-action/issues/42#issuecomment-915323168 + - name: Set Versions + uses: actions/github-script@v9 + id: set_version + with: + script: | + const tag = context.ref.substring(10) + const no_v = tag.replace('v', '') + const dash_index = no_v.lastIndexOf('-') + const no_dash = (dash_index > -1) ? no_v.substring(0, dash_index) : no_v + core.setOutput('tag', tag) + core.setOutput('no-v', no_v) + core.setOutput('no-dash', no_dash) + - name: Build and push tag ${{steps.set_version.outputs.no-dash}} + uses: docker/build-push-action@v7 + with: + context: . + file: docker/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + build-args: | + GIT_REVISION=${{ github.sha }} + GIT_TAG=${{steps.set_version.outputs.no-dash}} + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{steps.set_version.outputs.no-dash}} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest diff --git a/README.md b/README.md index 6971164..5b8d9c9 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ [![e2e-test](https://github.com/ks6088ts/template-typescript-react/actions/workflows/e2e-test.yaml/badge.svg?branch=main)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/e2e-test.yaml?query=branch%3Amain) [![docker](https://github.com/ks6088ts/template-typescript-react/actions/workflows/docker.yaml/badge.svg?branch=main)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/docker.yaml?query=branch%3Amain) [![ghcr](https://github.com/ks6088ts/template-typescript-react/actions/workflows/ghcr-release.yaml/badge.svg)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/ghcr-release.yaml) +[![docker-release](https://github.com/ks6088ts/template-typescript-react/actions/workflows/docker-release.yaml/badge.svg)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/docker-release.yaml) [![github-pages](https://github.com/ks6088ts/template-typescript-react/actions/workflows/github-pages.yaml/badge.svg?branch=main)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/github-pages.yaml?query=branch%3Amain) [![release](https://github.com/ks6088ts/template-typescript-react/actions/workflows/release.yaml/badge.svg)](https://github.com/ks6088ts/template-typescript-react/actions/workflows/release.yaml) [![GitHub release](https://img.shields.io/github/v/release/ks6088ts/template-typescript-react?logo=github&label=release)](https://github.com/ks6088ts/template-typescript-react/releases/latest) +[![Docker Hub](https://img.shields.io/docker/v/ks6088ts/template-typescript-react?logo=docker&label=Docker%20Hub&sort=semver)](https://hub.docker.com/r/ks6088ts/template-typescript-react) ![React](https://img.shields.io/badge/React-19-61DAFB?logo=react&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) @@ -98,7 +100,24 @@ For Docker-focused CI checks, run: make ci-test-docker ``` -Pushing a `v*` tag keeps the existing release asset flow and also publishes a multi-arch image to `ghcr.io/ks6088ts/template-typescript-react`. +### Published images + +Pushing a `v*` tag keeps the existing release asset flow and also publishes multi-arch (`linux/amd64`, `linux/arm64`) images to both registries: + +| Registry | Image | Workflow | +| --- | --- | --- | +| GitHub Container Registry | `ghcr.io/ks6088ts/template-typescript-react` | [ghcr-release.yaml](.github/workflows/ghcr-release.yaml) | +| Docker Hub | `ks6088ts/template-typescript-react` | [docker-release.yaml](.github/workflows/docker-release.yaml) | + +```bash +# Pull and run from GitHub Container Registry +docker run --rm -p 8080:80 ghcr.io/ks6088ts/template-typescript-react:latest + +# Pull and run from Docker Hub +docker run --rm -p 8080:80 ks6088ts/template-typescript-react:latest +``` + +The Docker Hub workflow requires two repository secrets: `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` (a Docker Hub access token). ## Testing