Skip to content

Infrastructure Tech Spec

Overview

CalcTek Calculator runs in two environments:

  • Local development -- Docker Compose with Nginx reverse proxy and mkcert TLS.
  • Production -- Google Kubernetes Engine (GKE) with Nginx Ingress Controller and sslip.io for DNS.

Local Infrastructure (Docker Compose)

graph TB
    subgraph Host Machine
        Hosts["/etc/hosts<br/>*.dev.calctek-calc.ai -> 192.168.204.5"]
        LaunchDaemon["LaunchDaemon<br/>loopback alias 192.168.204.5"]
    end

    subgraph Docker Compose
        Nginx["Nginx<br/>:80, :443<br/>TLS termination"]
        BE["Backend<br/>Laravel PHP-FPM<br/>:9000"]
        FE["Frontend<br/>Vite Dev Server<br/>:5173"]
        Docs["Docs<br/>MkDocs<br/>:8000"]
        DB[(SQLite)]
    end

    Hosts --> Nginx
    Nginx -- "app.*" --> FE
    Nginx -- "api.* FastCGI" --> BE
    Nginx -- "docs.*" --> Docs
    BE --> DB

Services

Service Image Ports Notes
nginx nginx:alpine 80, 443 TLS via mkcert wildcard cert
backend Custom (PHP-FPM) 9000 docker/laravel/Dockerfile
frontend Custom (Node) 5173 frontend/Dockerfile target: development
docs squidfunk/mkdocs-material 8000 Live reload

Networking

  • External bridge network: calctek-calc-network
  • Local IP: 192.168.204.5 (loopback alias)
  • DNS: /etc/hosts entries for api.dev.calctek-calc.ai, app.dev.calctek-calc.ai, docs.dev.calctek-calc.ai
  • TLS: Wildcard certificate for *.dev.calctek-calc.ai via mkcert

Volumes

Volume Type Purpose
./backend:/var/www/html Bind mount Laravel source (also mounted in Nginx for FastCGI)
./frontend:/app Bind mount Vue source
./docs:/docs Bind mount MkDocs source
./docker/ssl:/ssl Bind mount (ro) TLS certificates
frontend_node_modules Named volume Isolate container node_modules from host

Production Infrastructure (GKE)

graph TB
    Internet --> LB["GCP Load Balancer"]
    LB --> IngressCtrl["Nginx Ingress Controller"]

    subgraph GKE Cluster
        IngressCtrl --> Ingress["Ingress Rules"]

        Ingress -- "app.*.sslip.io" --> FESvc["Frontend Service"]
        Ingress -- "api.*.sslip.io" --> BESvc["Backend Service"]
        Ingress -- "docs.*.sslip.io" --> DocsSvc["Docs Service"]

        FESvc --> FEPod["Frontend Pod<br/>Nginx + static files"]
        BESvc --> BEPod["Backend Pod<br/>Nginx sidecar + PHP-FPM"]
        DocsSvc --> DocsPod["Docs Pod<br/>Nginx + static files"]

        BEPod --> PVC["PVC<br/>SQLite DB"]
    end

    subgraph Artifact Registry
        Images["backend:latest<br/>frontend:latest<br/>docs:latest"]
    end

    Images -.-> FEPod
    Images -.-> BEPod
    Images -.-> DocsPod

GKE Cluster

Setting Value
Cluster name calctek-calc-cluster
Zone us-central1-a
Machine type e2-medium
Nodes 1
Disk size 30 GB

Kubernetes Resources

Manifest Resource Description
namespace.yaml Namespace calctek-calc namespace
cert-issuer.yaml ClusterIssuer Let's Encrypt cert-manager issuer
backend-pvc.yaml PVC Persistent volume for SQLite database
backend-deployment.yaml Deployment + ConfigMap Backend pods (PHP-FPM + Nginx sidecar)
frontend-deployment.yaml Deployment Frontend pods (Nginx serving static build)
docs-deployment.yaml Deployment Docs pods (Nginx serving MkDocs build)
backend-service.yaml Service ClusterIP for backend
frontend-service.yaml Service ClusterIP for frontend
docs-service.yaml Service ClusterIP for docs
ingress.yaml Ingress Host-based routing via Nginx Ingress
configmap.yaml ConfigMap Shared configuration

DNS (sslip.io)

Production uses sslip.io for DNS resolution without a registered domain. Given a LoadBalancer IP of 34.56.78.90, the URLs become:

Service URL
Frontend http://app.34-56-78-90.sslip.io
API http://api.34-56-78-90.sslip.io
Docs http://docs.34-56-78-90.sslip.io

Container Registry

Images are stored in Google Artifact Registry:

<region>-docker.pkg.dev/<project-id>/calctek-calc/<service>:latest

Secrets

Kubernetes secrets are created from .env values during deployment:

Secret Key Source
APP_KEY .env
GOOGLE_CLIENT_ID .env
GOOGLE_CLIENT_SECRET .env

Deployment

Local

make setup   # One-time setup
make start   # Start containers
make stop    # Stop containers

Production (GKE)

make gke-deploy-all   # Full deployment (init -> cluster -> build -> push -> deploy)
make gke-status       # Check pod status
make gke-urls         # Print live URLs
make gke-destroy      # Tear down everything

See the GKE Runbook for detailed operational procedures.