# Deployment Guide

# Bilko Deployment Guide

**Last Updated:** 2026-04-16  
**Current State:** Stable Cloud Run deployment with custom domain provisioning

## GCP Project Configuration

- **Project ID:** `tribal-sign-487920-k0`
- **Region:** `europe-north1` (Stockholm)
- **Services:**
    - `bilko-api` → https://bilko-api-dh4m46blja-lz.a.run.app (revision 00037)
    - `bilko-web` → https://bilko-web-dh4m46blja-lz.a.run.app

## Secret Manager

<table id="bkmrk-secret-nameversionpu"><thead><tr><th>Secret Name</th><th>Version</th><th>Purpose</th></tr></thead><tbody><tr><td>`bilko-cors-origins`</td><td>v2</td><td>Comma-separated list of allowed CORS origins</td></tr><tr><td>`bilko-database-url`</td><td>latest</td><td>Cloud SQL connection string (password reset 2026-04-16)</td></tr><tr><td>`bilko-jwt-refresh-secret`</td><td>latest</td><td>JWT refresh token secret</td></tr></tbody></table>

**CORS Parsing:** Secret `bilko-cors-origins` is parsed by comma in `apps/api/src/app.ts:61`

## Environment Variables

### bilko-web

- `NEXT_PUBLIC_API_URL=https://bilko-api-dh4m46blja-lz.a.run.app`

### bilko-api

- `CORS_ORIGINS` → pulled from `bilko-cors-origins:latest`
- `SESSION_COOKIE_SECURE=true`
- `NODE_ENV=production`
- `DATABASE_URL` → pulled from `bilko-database-url:latest`

## Custom Domain Setup

### Current Domain

- **Host:** `bilko-demo.alai.no`
- **Mapped to:** `bilko-web` Cloud Run service
- **DNS Provider:** one.com
- **DNS Record:** `CNAME bilko-demo.alai.no → ghs.googlehosted.com.`
- **TLS Cert:** Let's Encrypt (managed by GCP, auto-renews)
- **Provisioning Time:** 15-30 minutes after DNS propagation

### Domain Verification Constraint

**Critical:** Only `alai.no` is verified in GCP Search Console (via dev@alai.no).  
`basicconsulting.no` is NOT verified. All custom domains MUST use `*.alai.no` subdomains until `basicconsulting.no` is verified.

## Custom Domain Runbook

### Prerequisites

1. Domain must be verified in Google Search Console by the GCP account owner
2. DNS provider access (one.com for `alai.no`, Vercel for `basicconsulting.no`)
3. `gcloud` CLI authenticated: `gcloud auth login`

### Step-by-Step

#### 1. Create Domain Mapping

```
gcloud beta run domain-mappings create \
  --service=bilko-web \
  --domain=bilko-demo.alai.no \
  --region=europe-north1 \
  --project=tribal-sign-487920-k0
```

#### 2. Configure DNS

Add CNAME record at DNS provider:

```
Type: CNAME
Host: bilko-demo
Value: ghs.googlehosted.com.
TTL: 3600
```

#### 3. Wait for Certificate Provisioning

```
gcloud beta run domain-mappings describe bilko-demo.alai.no \
  --region=europe-north1 \
  --project=tribal-sign-487920-k0
```

Look for `status.conditions → CertificateProvisioned: True`

#### 4. Update CORS Allowed Origins

```
# Get current value
gcloud secrets versions access latest --secret=bilko-cors-origins

# Add new domain
echo "https://bilko-demo.alai.no,https://bilko-web-dh4m46blja-lz.a.run.app" | \
  gcloud secrets versions add bilko-cors-origins --data-file=-
```

#### 5. Deploy New Revision

```
gcloud run services update-traffic bilko-api \
  --to-latest \
  --region=europe-north1 \
  --project=tribal-sign-487920-k0
```

#### 6. Verify CORS Preflight

```
curl -sSI -X OPTIONS \
  https://bilko-api-dh4m46blja-lz.a.run.app/api/v1/auth/login \
  -H "Origin: https://bilko-demo.alai.no" \
  -H "Access-Control-Request-Method: POST"
```

**Expected:** HTTP 204 with `Access-Control-Allow-Origin: https://bilko-demo.alai.no`

## GitHub Actions CI/CD

- **Workflow:** `.github/workflows/deploy-production.yml`
- **Auth:** Workload Identity Federation (WIF)
- **Service Account:** `github-actions@tribal-sign-487920-k0.iam.gserviceaccount.com`
- **IAM Roles:** `roles/run.admin`, `roles/iam.serviceAccountUser`

### Deployment Steps

1. Authenticate via WIF
2. Build Docker images (api + web)
3. Push to Google Container Registry
4. Deploy to Cloud Run (europe-north1)
5. Run smoke tests (Playwright E2E)

## Testing

### Backend Tests

- **Framework:** Vitest
- **Location:** `apps/api/src/**/*.test.ts`
- **Command:** `pnpm test` (from `apps/api/`)

### End-to-End Tests

- **Framework:** Playwright
- **Location:** `apps/e2e/tests/`
- **Command:** `pnpm test` (from `apps/e2e/`)
- **Runs in CI:** Yes (on every deploy)

## Recent Fixes (2026-04-16)

### Commits

- `a62b7f6` — Cloud Run service names align: `bilko-staging-*` → `bilko-*`
- `9b1ced1` — Backend: added `currency` field to invoice list, added `/api/v1/settings/profile` endpoint
- `73693d4` — Frontend: avatar initials fallback, invoice step indicator, chat widget ARIA labels

### Key Changes

1. **Service Naming:** Production services now named `bilko-api` and `bilko-web` (no `-staging` suffix)
2. **API Enhancements:** Invoice list now includes currency, new profile settings endpoint
3. **Frontend Fixes:** Accessibility improvements (ARIA), avatar initials when no image, visual polish

## Rollback Procedure

### Rollback to Previous Revision

```
# List revisions
gcloud run revisions list --service=bilko-api --region=europe-north1

# Rollback
gcloud run services update-traffic bilko-api \
  --to-revisions=bilko-api-00036=100 \
  --region=europe-north1
```

### Rollback via GitHub Actions

```
git revert HEAD
git push origin main  # Triggers deploy workflow
```

## Troubleshooting

### Issue: CORS errors in browser

**Cause:** Custom domain not in `bilko-cors-origins` secret  
**Fix:** Update secret (see step 4 in Custom Domain Runbook), deploy new revision

### Issue: 502 Bad Gateway

**Cause:** Service unhealthy or startup timeout  
**Fix:** Check Cloud Run logs: `gcloud run services logs read bilko-api --region=europe-north1 --limit=50`

### Issue: Database connection timeout

**Cause:** Cloud SQL Proxy misconfiguration or secret outdated  
**Fix:** Verify `bilko-database-url` secret, check Cloud SQL instance status

### Issue: Custom domain SSL pending

**Cause:** DNS not propagated or domain not verified in Search Console  
**Fix:** Wait 15-30 min after DNS change, verify domain ownership in Search Console

## Architecture Diagram

```

┌─────────────────┐
│  one.com DNS    │
│  bilko-demo.    │
│  alai.no        │
└────────┬────────┘
         │ CNAME
         ▼
┌─────────────────────────────┐
│  ghs.googlehosted.com       │
│  (Google Cloud Load Balancer)│
└────────┬────────────────────┘
         │
         ▼
┌─────────────────────────────┐       ┌──────────────────┐
│  bilko-web (Cloud Run)      │──────▶│  bilko-api       │
│  Next.js 15 + React 19      │  HTTP │  Express + TS    │
│  europe-north1              │       │  europe-north1   │
└─────────────────────────────┘       └────────┬─────────┘
                                               │
                                               ▼
                                      ┌─────────────────┐
                                      │  Cloud SQL      │
                                      │  PostgreSQL 16  │
                                      │  europe-north1  │
                                      └─────────────────┘
```