Build & Deployment
The Admin UI is a static single-page application (SPA) built with Vite and served via Nginx inside a Docker container.
Environment Variables
The UI supports three environment files out of the box:
| File | Used when |
|---|---|
.env.development | pnpm dev |
.env.staging | pnpm build targeting staging |
.env.production | pnpm build targeting production |
Variables reference
| Variable | Required | Description |
|---|---|---|
VITE_API_URL | Yes | Backend API base URL (e.g. https://api.example.com) |
VITE_APP_NAME | No | Browser tab title |
VITE_APP_VERSION | No | Semantic version (injected by CI) |
VITE_GIT_COMMIT_HASH | No | Git commit SHA (injected by CI) |
VITE_FIREBASE_API_KEY | No* | Firebase push notifications |
VITE_FIREBASE_AUTH_DOMAIN | No* | Firebase push notifications |
VITE_FIREBASE_PROJECT_ID | No* | Firebase push notifications |
VITE_FIREBASE_STORAGE_BUCKET | No* | Firebase push notifications |
VITE_FIREBASE_MESSAGING_SENDER_ID | No* | Firebase push notifications |
VITE_FIREBASE_APP_ID | No* | Firebase push notifications |
VITE_FIREBASE_VAPID_KEY | No* | Firebase push notifications |
*Firebase variables are optional — push notifications are disabled if omitted.
VITE_* variables are embedded into the compiled JS bundle at build time. Never put secrets (private keys, database credentials) in these variables.
Production Build
pnpm build
This runs TypeScript type-checking first (tsc --noEmit), then Vite compiles and bundles the app into the dist/ directory.
Preview the production build locally:
pnpm preview
# App available at http://localhost:4173
Build output
dist/
├── index.html
├── assets/
│ ├── index-[hash].js # Main bundle (code-split)
│ ├── index-[hash].css # Tailwind styles
│ └── ... # Lazy-loaded route chunks
└── favicon.ico
TanStack Router automatically splits each route into its own chunk, so only the code for the current page is loaded on first render.
Docker
The project ships a multistage Dockerfile:
Stage 1: node:20-alpine → Build the Vite app
Stage 2: nginx:alpine → Serve the built dist/
Build the image
docker build \
--build-arg VITE_API_URL=https://api.example.com \
--build-arg VITE_APP_VERSION=1.0.0 \
--build-arg VITE_GIT_COMMIT_HASH=$(git rev-parse --short HEAD) \
-t association-app-frontend:latest .
Run the container
docker run -p 80:80 \
-e API_BACKEND_URL=https://api.example.com \
association-app-frontend:latest
The API_BACKEND_URL runtime variable is injected into the Nginx configuration at container startup via envsubst. This allows the same Docker image to be deployed to different environments by changing only the environment variable — no rebuild required.
Docker entrypoint
On startup, docker-entrypoint.sh performs two steps before handing off to Nginx:
- Injects
API_BACKEND_URLinto the Nginx config template usingenvsubst - Writes
runtime-config.jscontainingVITE_APP_VERSIONandVITE_GIT_COMMIT_HASHfor in-app display
Nginx configuration
| Feature | Detail |
|---|---|
| Port | 80 |
| SPA routing | try_files $uri $uri/ /index.html (all unknown paths serve index.html) |
| API proxy | /api/* → $API_BACKEND_URL |
| Static asset cache | max-age=31536000 (1 year) for JS/CSS/images |
| Gzip | Enabled for HTML, JS, CSS, JSON, SVG |
| Health check | GET /health → 200 OK |
CI/CD Pipeline
CI is handled by GitHub Actions (.github/workflows/docker-build.yml).
Triggers
| Event | Environment |
|---|---|
Push to develop branch | Development |
Push to staging branch | Staging |
Push to main branch | Production |
Push a version tag (v*) | Production |
| Manual dispatch | Selectable |
Pipeline steps
- Checkout source code
- Authenticate with Google Artifact Registry (australia-southeast2)
- Docker build with appropriate
VITE_API_URLfor the target environment - Push image to
association-app/frontendregistry - Semantic release (main branch only) — bumps version, generates CHANGELOG, creates GitHub release
Image tagging strategy
| Branch / Trigger | Image tag |
|---|---|
develop | develop-{commit-sha} |
staging | staging-{commit-sha} |
main | latest, {semver} |
Version tag v1.2.3 | 1.2.3, latest |
Alternative Hosting Options
The dist/ output is a standard static site and can be hosted on any static hosting provider.
Netlify
A netlify.toml is included. Deploy by connecting the repository to Netlify and setting environment variables in the Netlify dashboard.
# netlify.toml (already in repo)
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Firebase Hosting
A firebase.json config is included for Firebase Hosting. Deploy with:
firebase deploy --only hosting
Other platforms (Vercel, Cloudflare Pages, etc.)
Set the build command to pnpm build and the publish directory to dist. Configure a catch-all redirect to index.html for SPA routing to work correctly.
Semantic Versioning & Releases
The project uses semantic-release for automated versioning on the main branch. Commit messages must follow Conventional Commits format:
| Commit prefix | Version bump |
|---|---|
fix: | Patch (1.0.x) |
feat: | Minor (1.x.0) |
feat!: or BREAKING CHANGE: | Major (x.0.0) |
On a successful main build, semantic-release will:
- Determine the next version from commit history
- Update
CHANGELOG.md - Create a git tag (e.g.
v1.3.0) - Publish a GitHub release with release notes