From 2b83e3ad0ec67a16fef2c517bf903109a7049699 Mon Sep 17 00:00:00 2001 From: Shad Date: Sun, 14 Jun 2026 00:10:56 +0400 Subject: [PATCH] deploy: Dockerfile + nginx + Gitea Actions build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two-stage build (node:22 → nginx:1.27) bakes dist/ into a static image. nginx reverse-proxies /api/* to demo.flow-master.ai so live mode works same-origin without CORS. CI runs vitest + build, then publishes gitea.flow-master.ai/shad/mission-control-demo:sha-${git} on push to main. Constraint: backend rejects cross-origin → same-origin proxy required Confidence: high Scope-risk: narrow Not-tested: image actually built in Gitea Actions (requires registry secret) --- .gitea/workflows/build.yml | 49 ++++++++++++++++++++++++++++++++++++++ Dockerfile | 18 ++++++++++++++ nginx.conf | 31 ++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 .gitea/workflows/build.yml create mode 100644 Dockerfile create mode 100644 nginx.conf diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..8ec5e3c --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,49 @@ +name: build-and-publish +on: + push: + branches: [main] + tags: ['v*'] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: { version: 9 } + - uses: actions/setup-node@v4 + with: { node-version: 22, cache: pnpm } + - run: pnpm install --frozen-lockfile + - run: pnpm test + - run: pnpm build + + image: + needs: test + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Compute tag + id: tag + run: | + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" + else + echo "tag=sha-${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT" + fi + - name: Login to Gitea registry + uses: docker/login-action@v3 + with: + registry: gitea.flow-master.ai + username: ${{ gitea.actor }} + password: ${{ secrets.GITEA_TOKEN }} + - uses: docker/setup-buildx-action@v3 + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + gitea.flow-master.ai/shad/mission-control-demo:${{ steps.tag.outputs.tag }} + gitea.flow-master.ai/shad/mission-control-demo:latest diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fb14870 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# Two-stage build: vite static build → nginx serving dist/. +# Image is intended to be pushed to gitea.flow-master.ai/shad/mission-control-demo +# and referenced from FM06/flowmaster-ops manifests/overlays/demo/mc-deployment.yaml. + +FROM node:22-alpine AS build +WORKDIR /app +COPY package.json pnpm-lock.yaml ./ +RUN corepack enable && corepack prepare pnpm@latest --activate \ + && pnpm install --frozen-lockfile +COPY . . +RUN pnpm build + +FROM nginx:1.27-alpine +COPY --from=build /app/dist/ /usr/share/nginx/html/ +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \ + CMD wget -q --spider http://127.0.0.1/ || exit 1 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..a1f6540 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,31 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Reverse-proxy /api to the demo backend so live mode works + # same-origin without CORS gymnastics. Backend is reached via the + # cluster-internal service rewritten by the ingress to demo.flow-master.ai. + location /api/ { + proxy_pass https://demo.flow-master.ai; + proxy_set_header Host demo.flow-master.ai; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_ssl_server_name on; + proxy_http_version 1.1; + proxy_read_timeout 30s; + } + + # SPA: everything else falls back to index.html. + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache"; + } + + # Static assets with content hashes in their filenames cache forever. + location /assets/ { + try_files $uri =404; + add_header Cache-Control "public, max-age=31536000, immutable"; + } +}