DEPLOYMENT ARCHITECTURE
VietGara — Multi-Tenant SaaS Platform for Auto Garage Management & Maintenance
Version 1.7 — October 10, 2026 · Cloud: Google Cloud Platform (GCP) · Backend: Go
1. Introduction
This document describes VietGara’s deployment architecture: environments, the CI/CD strategy, release strategy, and cluster/namespace structure on GKE Autopilot (ADR-009).
2. Environments
| Environment | Purpose | Characteristics |
|---|---|---|
| dev | Development, continuous integration | Planned but not currently provisioned; when available, uses mock data and GitOps-managed deployments |
| staging | Pre-production testing, stakeholder demos | Configuration close to production, uses third-party sandboxes (VNPay sandbox, VNPT test environment) |
| production | Serves real users | Deployed only through an approved pipeline (approval gate), monitored 24/7 |
| Service | local (Docker Compose) | dev / staging | production |
|---|---|---|---|
| PostgreSQL | postgres container | Cloud SQL | Cloud SQL, regional HA |
| Application DB login | APP_DB_USER, created on startup in vietgara_app | Created once in vietgara_app, password in Secret Manager | Same as staging |
| Redis | redis container | Memorystore | Memorystore (HA) |
| Kafka | apache/kafka KRaft, single broker | Managed Kafka (small cluster) | Managed Kafka, 3 brokers, replication factor 3 |
| Mailpit (inbox on :8025) | Mailjet, sender [email protected] | Mailjet, sender [email protected] (Section 2.2) | |
| File storage | Local volume | Local persistent volume | Cloud Storage bucket |
| Gateway | Kong (DB-less, kong/kong.yml) | Kong on GKE | Kong on GKE behind Cloud Load Balancing + Cloud Armor |
2.1. Interim staging: single VM (until the GKE/Terraform migration)
The table above is the target. Until the GKE environments and Terraform are in place (Release 9.2), staging runs on one VM with Docker Compose:
| Concern | Interim staging |
|---|---|
| Images | GitHub Actions in vietgara-backend, vietgara-owner-web and vietgara-admin-web builds each image after its checks pass and pushes it on merges to master and v* tags as a public package to the GitHub Container Registry (ghcr.io/viettechno/…), tagged with git describe. make image-push does the same by hand |
| Services | PostgreSQL, Redis and single-broker Kafka as containers with named volumes; Kong (DB-less) behind Caddy, which terminates TLS with Let’s Encrypt |
| Mailjet (staging sender), or Mailpit on the VM for QA runs | |
| File storage | Docker volume on the VM disk |
| Configuration | vietgara-infra/staging/: Compose file, .env.example, Kong and Caddy config, scripts. Secrets in .env on the VM only |
| Deployment | Manual on the VM: make deploy BACKEND_TAG=… WEB_OWNER_TAG=… on the VM; migrations run at backend start-up as in Section 3 |
The two web images install a git dependency (@vietgara/web-ui, a tag over HTTPS), so their Dockerfiles install git; vietgara-web-ui has its own CI (format, lint, typecheck, token contrast test, gallery build) and is released by tag, not deployed. The platform admin web runs as its own service on its own host: vietgara-admin-web in the staging Compose file (site WEB_ADMIN_ADDRESS, /api through Kong) and admin.localhost in the backend’s local Compose stack.
Step-by-step guide: vietgara-infra/staging/README.md (summary in 06-Operations/Deployment_Guide.md). When staging moves to GKE, the images move to Artifact Registry and deployment to the GitOps flow below.
2.2. Hostnames
Every public address is a host directly under viettechno.com, the company’s domain, which other products share. The hosts are named <product>[-<app>][-<env>]: the product first, then the app, then the environment, with no suffix in production. A later -dev environment follows the same rule.
| Purpose | Production | Staging | Local |
|---|---|---|---|
Garage owner web, its /api (same origin) and the public quote links (/public/quotes/{token}) | vietgara.viettechno.com | vietgara-staging.viettechno.com | localhost:8000 |
Platform admin and its /api (same origin) | vietgara-admin.viettechno.com | vietgara-admin-staging.viettechno.com | admin.localhost:8000 |
API only (/api, nothing else): mobile apps, blackbox suite, integrations | vietgara-api.viettechno.com | vietgara-api-staging.viettechno.com | localhost:8000 (10.0.2.2:8000 from the Android emulator) |
| Test inbox (Mailpit, basic auth) | — | vietgara-mailpit-staging.viettechno.com | localhost:8025 |
| Company homepage and product pages (static; Release 10.2) | viettechno.com, www.viettechno.com | preview deploys per pull request | localhost:4321 |
| User guide (static; Release 10.2) | vietgara-help.viettechno.com | preview deploys per pull request | localhost:4321 |
| E-mail sender (Mailjet) | [email protected] | [email protected] | Mailpit |
| Support address shown to users | [email protected] | same | same |
- The web apps keep calling
/apion their own origin. There is no CORS, each app’s tokens stay in its own origin, and the Google sign-in popup posts its result to the same origin. They never call the API host. - The API host serves the same
/api/v1/…paths, so a client only changes its base URL; the mobile apps are built withAPI_BASE_URL=https://vietgara-api.viettechno.com/api. A dedicated host lets rate limits, a WAF or a separate gateway apply to mobile and integration traffic without touching the web apps. - E-mail comes from the product’s own subdomain, validated in Mailjet with its own SPF and DKIM records, so the product’s sending reputation and bounces stay apart from the company mailboxes on
viettechno.com. - One level under
viettechno.com: every host is a plain A record in the company zone (PA Vietnam), and a single*.viettechno.comcertificate would cover them all; today Caddy obtains one Let’s Encrypt certificate per host. - Google sign-in registers each owner web host as an authorized JavaScript origin, and
https://<host>/api/v1/auth/google/callbackas a redirect URI;viettechno.comis the authorized domain of the consent screen. - The public sites are static and outside the stack. They are hosted by a static host chosen in ADR-013, with CNAME records in the company zone; the apex
viettechno.comalso carries the company mailboxes (MX, SPF), which stay untouched. The sites have no staging host: each pull request gets a preview URL, andmasteris production. - The contact form never calls the API host from a browser. The site’s own
/contactfunction forwards the request server to server (FR-WEB-04), so the API host has no CORS configuration. - The API’s error domain (
google.rpc.ErrorInfo.domain) isvietgara-api.viettechno.com. - Mobile app identifiers are the reverse DNS of the product,
com.viettechno.vietgara.<app>(Coding Standards Section 4.2a). - Test data uses reserved names:
vietgara.testfor E2E,blackbox.vietgara.testfor the blackbox suite’s test users. Never a real domain.
3. CI/CD Pipeline
flowchart TD
pr["Pull request / push to master"] --> ci
subgraph ci["GitHub Actions (per repository)"]
be["vietgara-backend<br/>build, vet, golangci-lint,<br/>unit tests (90% gate), integration tests"]
fe["vietgara-owner-web / vietgara-admin-web<br/>format, lint, typecheck, tests,<br/>API types check, build, image"]
ma["vietgara-owner-app / vietgara-admin-app<br/>format, analyze, test"]
pt["vietgara-proto<br/>buf lint/format, generated-code check,<br/>go build, buf breaking"]
end
ci --> img["Build image → Container Analysis scan<br/>→ Artifact Registry (tag = git SHA)"]
img --> cfg["Update image tag in the GitOps config repo"]
cfg --> argo[ArgoCD]
argo -. "planned; not provisioned" .-> dev["dev: auto-sync"]
argo --> stg["staging: auto-sync after merge to the release branch"]
argo --> prod["production: manual approval in ArgoCD"]
Database migrations run at backend start-up with the schema-owner connection (MIGRATION_DATABASE_URL) before the new version serves traffic; they must be backward compatible with the previous version (expand–contract, Section 6).
3.1. GitOps
All Kubernetes configuration (Deployment, Service, ConfigMap, HPA, etc.) is managed as Helm charts in a separate Git repository (not mixed with service code). ArgoCD continuously reconciles the actual cluster state against the declared state in Git, auto-syncing (dev/staging) or waiting for approval (production).
4. Release Strategy
| Strategy | Applies To |
|---|---|
| Rolling update (Kubernetes default) | Most backend services with no breaking API changes |
| Canary release (10% of traffic first, gradually increasing) | High-risk-of-failure services (Billing & Settlement, Payment Gateway Adapter, E-Invoice Service) |
| Blue-Green (deploy in parallel, cut traffic over instantly) | Major database schema changes or breaking API Gateway changes |
| Feature flags (LaunchDarkly or built on top of Entitlements) | New features that need to be toggled per garage without redeploying |
5. GKE Namespace Structure
flowchart TB
subgraph cluster["Cluster vietgara-prod (GKE Autopilot, asia-southeast1)"]
gw["gateway<br/>Kong"]
core["core-services<br/>Identity, Tenant, License, Customer & Vehicle, Quote, Repair Order, Catalogue, Files, Supplier<br/>(Phase 1: one modular-monolith Deployment runs modules 1–9 of the HLD, Section 3)"]
fin["finance-services<br/>Billing, Receivables, Payment Adapter, E-Invoice"]
sup["support-services<br/>Inventory, Notification, Document, Reporting, Insurance, Booking"]
bff["bff<br/>Customer BFF, Technician BFF"]
plat["platform<br/>ArgoCD, monitoring agents, cert-manager"]
end
gw --> core & fin & sup & bff
Each service has a Deployment (minimum 2 replicas), a HorizontalPodAutoscaler (CPU and consumer lag), and a PodDisruptionBudget (at least 1 replica during node maintenance). Several backend replicas can run the outbox relay and the license expiry job concurrently: the relay locks rows with FOR UPDATE SKIP LOCKED and the job’s transitions are idempotent.
6. Rollback
- ArgoCD retains sync history — rolling back to a previous version is a single command/click, with no need to rebuild the image.
- Database migrations use a tool that supports rollback (e.g. golang-migrate) with a down.sql script for each migration; breaking migrations (dropping columns, changing data types) are split into two steps (the expand-contract pattern) to avoid emergency schema rollbacks.
- Readiness/liveness health-check probes are mandatory for every service — Kubernetes automatically stops routing traffic to a failing pod before an operator can react.
7. Pre-Deployment Testing Strategy
| Test Type | Environment | Required Before |
|---|---|---|
| Unit tests | CI (GitHub Actions) | Code merge |
Integration tests (Postgres/RLS as vietgara_app, migrations up/down) | CI (testcontainers) | Code merge |
Contract checks (buf breaking, generated code and API types in sync) | CI | Code merge |
| Contract tests (API Gateway <-> BFF <-> mobile) | staging | Release to production |
| Load testing (meeting NFR-PF-06: 50,000 transactions/day) | staging | Major releases/infrastructure changes |
| Security scanning (SAST/DAST + container scan) | CI + staging | Code merge / release |