Skip to content

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

EnvironmentPurposeCharacteristics
devDevelopment, continuous integrationPlanned but not currently provisioned; when available, uses mock data and GitOps-managed deployments
stagingPre-production testing, stakeholder demosConfiguration close to production, uses third-party sandboxes (VNPay sandbox, VNPT test environment)
productionServes real usersDeployed only through an approved pipeline (approval gate), monitored 24/7
Servicelocal (Docker Compose)dev / stagingproduction
PostgreSQLpostgres containerCloud SQLCloud SQL, regional HA
Application DB loginAPP_DB_USER, created on startup in vietgara_appCreated once in vietgara_app, password in Secret ManagerSame as staging
Redisredis containerMemorystoreMemorystore (HA)
Kafkaapache/kafka KRaft, single brokerManaged Kafka (small cluster)Managed Kafka, 3 brokers, replication factor 3
E-mailMailpit (inbox on :8025)Mailjet, sender [email protected]Mailjet, sender [email protected] (Section 2.2)
File storageLocal volumeLocal persistent volumeCloud Storage bucket
GatewayKong (DB-less, kong/kong.yml)Kong on GKEKong 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:

ConcernInterim staging
ImagesGitHub 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
ServicesPostgreSQL, Redis and single-broker Kafka as containers with named volumes; Kong (DB-less) behind Caddy, which terminates TLS with Let’s Encrypt
E-mailMailjet (staging sender), or Mailpit on the VM for QA runs
File storageDocker volume on the VM disk
Configurationvietgara-infra/staging/: Compose file, .env.example, Kong and Caddy config, scripts. Secrets in .env on the VM only
DeploymentManual 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.

PurposeProductionStagingLocal
Garage owner web, its /api (same origin) and the public quote links (/public/quotes/{token})vietgara.viettechno.comvietgara-staging.viettechno.comlocalhost:8000
Platform admin and its /api (same origin)vietgara-admin.viettechno.comvietgara-admin-staging.viettechno.comadmin.localhost:8000
API only (/api, nothing else): mobile apps, blackbox suite, integrationsvietgara-api.viettechno.comvietgara-api-staging.viettechno.comlocalhost:8000 (10.0.2.2:8000 from the Android emulator)
Test inbox (Mailpit, basic auth)—vietgara-mailpit-staging.viettechno.comlocalhost:8025
Company homepage and product pages (static; Release 10.2)viettechno.com, www.viettechno.compreview deploys per pull requestlocalhost:4321
User guide (static; Release 10.2)vietgara-help.viettechno.compreview deploys per pull requestlocalhost:4321
E-mail sender (Mailjet)[email protected][email protected]Mailpit
Support address shown to users[email protected]samesame
  • The web apps keep calling /api on 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 with API_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.com certificate 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/callback as a redirect URI; viettechno.com is 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.com also carries the company mailboxes (MX, SPF), which stay untouched. The sites have no staging host: each pull request gets a preview URL, and master is production.
  • The contact form never calls the API host from a browser. The site’s own /contact function 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) is vietgara-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.test for E2E, blackbox.vietgara.test for 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

StrategyApplies 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 TypeEnvironmentRequired Before
Unit testsCI (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)CICode merge
Contract tests (API Gateway <-> BFF <-> mobile)stagingRelease to production
Load testing (meeting NFR-PF-06: 50,000 transactions/day)stagingMajor releases/infrastructure changes
Security scanning (SAST/DAST + container scan)CI + stagingCode merge / release