Skip to content

Deployment Guide

Captain Dany edited this page Jun 11, 2026 · 1 revision

Deployment Guide

CI/CD Pipeline Architecture

                    ┌─────────────┐
                    │ Push to dev │
                    └──────┬──────┘
                           │
              ┌────────────┴────────────┐
              ▼                         ▼
         ┌─────────┐             ┌──────────┐
         │  CI     │             │   CD     │
         │ (all    │             │ (deploy  │
         │ checks) │             │  to dev) │
         └─────────┘             └────┬─────┘
                                      │
                           ┌──────────┴──────────┐
                           ▼                     ▼
                    ┌──────────┐          ┌──────────┐
                    │ Staging  │          │Production│
                    │ (manual  │          │ (manual  │
                    │approval) │          │approval) │
                    └──────────┘          └──────────┘

CI Pipeline (.github/workflows/ci.yml)

Runs on every push/PR to main/dev:

  • Path filtering (backend, frontend, docker)
  • Go lint (golangci-lint), test (with coverage), build
  • Frontend build (npm ci && npm run build)
  • Docker build check (no push)
  • Security audit (govulncheck)
  • Dependency review (on PRs)

CD Pipeline (.github/workflows/cd.yml)

Triggers on push to dev/main or workflow_dispatch:

  1. Calculate Version — semantic version from git tags
  2. Build & Push Image — Docker build, push to ghcr.io, attestation
  3. Deploy to Dev — Helm install to oscar-dev namespace (automatic)
  4. Deploy to Staging — Manual approval gate → Helm install to oscar-staging
  5. Deploy to Production — Manual approval gate → Helm install to oscar-production

Prerequisites

Cluster

# Create namespace
kubectl create namespace oscar-dev
kubectl create namespace oscar-staging

# Create ghcr pull secret (required for image pull)
kubectl create secret docker-registry ghcr-pull \
  --docker-server=ghcr.io \
  --docker-username=CaptDany \
  --docker-password=<GH_PAT> \
  -n oscar-dev
kubectl create secret docker-registry ghcr-pull \
  --docker-server=ghcr.io \
  --docker-username=CaptDany \
  --docker-password=<GH_PAT> \
  -n oscar-staging

# Create app secret (at least 32 characters)
$secret = -join ((65..90)+(97..122)+(48..57) | Get-Random -Count 64 | % {[char]$_})
kubectl create secret generic oscar-app-secret \
  --from-literal=app-secret=$secret \
  -n oscar-dev

# Create service account (Helm handles this, but if deploying manually without Helm...)

GitHub Secrets

Secret Description
SA_TOKEN Service account token (kube-system/github-actions) for programmatic kubeconfig
GITHUB_TOKEN Auto-created, has push/pull permissions

The old KUBECONFIG_DEV, KUBECONFIG_STAGING, KUBECONFIG_PROD secrets have been removed. The workflow builds the kubeconfig programmatically using kubectl config commands with the SA_TOKEN secret.

Service Account Setup

# Create service account (one-time)
kubectl create serviceaccount github-actions -n kube-system

# Bind cluster-admin role
kubectl create clusterrolebinding github-actions-admin \
  --clusterrole=cluster-admin \
  --serviceaccount=kube-system:github-actions

# Create long-lived token
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: github-actions-token
  namespace: kube-system
  annotations:
    kubernetes.io/service-account.name: github-actions
type: kubernetes.io/service-account-token
EOF

# Get the token
$tokenB64 = kubectl get secret github-actions-token -n kube-system -o jsonpath='{.data.token}'
$token = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($tokenB64))
$token | gh secret set SA_TOKEN --repo CaptDany/oscar

Deploying

Automatic (push to dev)

Push to dev branch triggers full CI + CD to Dev.

Manual (workflow_dispatch)

gh workflow run CD --repo CaptDany/oscar --ref dev \
  -f environment=staging

Helm Chart Structure

deploy/helm/opencrm/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── _helpers.tpl
│   ├── deployment.yaml          # Deployment + Service + Secret
│   ├── namespace-resources.yaml # Postgres/Redis (conditional)
│   ├── serviceaccount.yaml      # ServiceAccount
│   ├── ingress.yaml             # Ingress
│   └── hpa.yaml                 # HPA (conditional)

Key Overrides

The CD pipeline overrides these Helm values:

  • image.repository — ghcr.io/captdany/oscar (lowercase!)
  • image.tag — semantic version (no + character, Docker rejects it)
  • secret.data.database-url — Postgres connection string
  • env.APP_ENV / env.APP_BASE_URL — per environment

Troubleshooting Deployments

Symptom Cause Fix
exec: executable oci not found Kubeconfig uses OCI exec auth Switch to SA token (kubectl config set-credentials)
InvalidImageName: must be lowercase Image repo has uppercase Use captdany/oscar not CaptDanny/oscar
invalid reference format Docker tag has + Replace + with - in semver
serviceaccount not found Missing SA template Add serviceaccount.yaml to Helm chart
APP_SECRET is required Missing env var Create oscar-app-secret k8s secret
context deadline exceeded Deployment not ready Increase --timeout, check pod logs
Failed to ping database No DATABASE_URL Set secret.data.database-url or deploy Postgres

Clone this wiki locally