---
title: "Troubleshooting Playbooks"
description: "Failure pattern → diagnosis → fix for FastAPI, Docker, React/Vite, Azure AD, Apache, PostgreSQL across Oliver projects"
tags: [architecture, troubleshooting, debugging, fastapi, docker, apache]
created: 2026-04-27
updated: 2026-04-27
---

# Troubleshooting Playbooks

Quick lookup: symptom → confirmed root cause → exact fix. Updated from real incidents.

## Key Takeaways

- Most 502s on optical-dev are Apache config errors, not dead containers
- Container "running" ≠ app healthy — always check `docker logs`
- Azure AD 403s are usually wrong auth flow type, not wrong permissions
- `alembic upgrade head` silently succeeds even if DB isn't ready — always wait for pg_isready first
- Next.js basePath must be in ALL redirect strings — middleware doesn't auto-prepend

---

## FastAPI

### 500 Internal Server Error on startup

```bash
docker logs {container} --tail 50
```

Common causes:
| Symptom | Cause | Fix |
|---------|-------|-----|
| `ModuleNotFoundError` | Missing package in requirements.txt | `pip install X && freeze` |
| `Could not connect to postgres` | DB container not ready | Add pg_isready loop before uvicorn |
| `pydantic ValidationError` on startup | Missing env var | Check `.env` vs `.env.example` diff |
| `Address already in use` | Port conflict | Check `docker ps` + port table |

### CORS errors from browser

```python
# app/main.py — must list exact origins, no trailing slash
app.add_middleware(CORSMiddleware,
    allow_origins=["http://localhost:5173", "https://optical-dev.oliver.solutions"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
```

Never use `allow_origins=["*"]` with `allow_credentials=True` — browser blocks it.

### `max_tokens` rejected by OpenAI

Newer OpenAI models (`o1`, `o3`, `gpt-4o`) reject `max_tokens` — use `max_completion_tokens` instead. See [[wiki/concepts/openai-max-completion-tokens|openai-max-completion-tokens]].

---

## Docker / Docker Compose

### Container exits immediately (exit code 1)

```bash
docker compose logs api         # see actual error
docker compose ps               # check status
```

### `docker compose up` pulls old image despite code change

```bash
docker compose build --no-cache api   # force rebuild
docker compose up -d
```

### Volume permissions error (PostgreSQL won't start)

```bash
docker compose down -v    # destroy volumes (loses data in dev)
docker compose up -d
```

### Build fails with `COPY failed: file not found`

Check that Dockerfile paths are relative to build context. Typical mistake: `COPY requirements.txt .` when build context is repo root but requirements.txt is in `backend/`.

```yaml
# docker-compose.yml
services:
  api:
    build:
      context: .          # repo root
      dockerfile: backend/Dockerfile
```

### Port already in use on server

```bash
sudo ss -tlnp | grep :8010    # find who holds the port
docker ps --format "{{.Names}} {{.Ports}}"
```

---

## React / Vite

### Blank page after deploy to optical-dev subpath

Missing `VITE_BASE_PATH` in build or React Router basename. See [[wiki/concepts/nextjs-basepath-auth-redirects|nextjs-basepath-auth-redirects]].

```bash
# Build must include base path
VITE_BASE_PATH=/my-project npm run build

# vite.config.ts
base: process.env.VITE_BASE_PATH ?? "/"

# main.tsx
<BrowserRouter basename={import.meta.env.VITE_BASE_PATH ?? "/"}>
```

### API calls return 404 in prod (work in dev)

All fetch calls must include the subpath prefix in prod:

```ts
const API = import.meta.env.VITE_BASE_PATH ?? "";
fetch(`${API}/api/jobs`)   // NOT: fetch("/api/jobs")
```

### TypeScript build fails on CI but not locally

Usually an import that works with loose tsconfig locally. Run:

```bash
npx tsc --noEmit    # strict check without building
```

Common: unused variables → prefix with `_`. Import of non-existent export → check barrel files.

---

## Azure AD / MSAL

### 403 `ErrorAccessDenied` from Graph API

Not a permissions problem — it's the wrong auth flow. See [[wiki/connections/graph-api-vs-msal-app-vs-delegated|graph-api-vs-msal-app-vs-delegated]].

- If **script/server task** (no user) → use app-only (client credentials) with Application permissions
- If **user-facing app** → use delegated (MSAL PKCE) with Delegated permissions

### `state mismatch` on MSAL redirect

See [[wiki/concepts/msal-vanilla-js-pkce|msal-vanilla-js-pkce]]. Causes:
- Multiple MSAL instances initialized (check for duplicate `new PublicClientApplication()`)
- Redirect URI in Azure Portal doesn't exactly match (trailing slash matters)
- Wrong platform type in Azure Portal — SPAs need **Single Page Application** platform, not Web

### Azure AD login works locally but fails on server

Redirect URI registered in Azure Portal must include the production URL. Checklist:
1. Azure Portal → App Registration → Authentication → Redirect URIs
2. Add `https://optical-dev.oliver.solutions/project/auth/callback`
3. `VITE_AZURE_REDIRECT_URI` in production `.env` must match exactly

### `DISABLE_AUTH=true` not working on server

Check that env var is in the correct service's `.env`, not root `.env`. In Docker Compose:
```yaml
env_file: ./backend/.env    # not ./. env
```

---

## Apache on optical-dev

### 502 Bad Gateway

Almost always Apache config, not a dead container. See [[wiki/concepts/proxmox-container-502-misdiagnosis|proxmox-container-502-misdiagnosis]] for the general pattern.

Checklist:
```bash
# 1. Is the container actually running?
ssh optical-dev "docker ps | grep {project}"

# 2. Is the app listening on the expected port?
ssh optical-dev "curl -s http://localhost:8010/health"

# 3. Is Apache config valid?
ssh optical-dev "sudo apache2ctl configtest"

# 4. Check Apache error log
ssh optical-dev "sudo tail -50 /var/log/apache2/error.log"
```

Common config mistakes:
- `ProxyPass /project/api/` before `Alias /project` → ProxyPass wins for everything → 502 on static files
- Missing `proxy_http` module: `sudo a2enmod proxy proxy_http && systemctl reload apache2`
- Include fragment added to wrong location in vhost (after `</VirtualHost>`)

### 404 on SPA deep links (works on `/` but not `/project/dashboard`)

SPA routing needs `FallbackResource` or RewriteRule to serve index.html:

```apache
<Directory /var/www/html/my-project>
    RewriteEngine On
    RewriteBase /my-project/
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ index.html [L]
</Directory>
```

### Apache Include ordering conflict

If a previous project's Apache config has a catch-all `ProxyPass / http://...` — it will intercept all requests including your new project. Fix: reorder Include lines so your project's specific paths come before catch-alls.

```bash
# Check current Include order
ssh optical-dev "cat /etc/apache2/sites-available/optical-dev.oliver.solutions.conf | grep Include"
```

---

## Git on Servers

### `git pull` fails with `Permission denied` after `sudo git clone`

`sudo git clone` creates all files owned by root. Subsequent user-level operations fail immediately.

```bash
# Diagnosis
ls -la /path/to/repo/.git   # owner = root

# Fix
sudo chown -R $USER:$USER /path/to/repo
git pull   # now works
```

Always clone without `sudo`:
```bash
git clone https://github.com/org/repo.git /path/to/repo   # no sudo
```

---

## PostgreSQL / Alembic

### Alembic `Can't locate revision` error

Migration files out of sync. Fix:
```bash
docker compose exec api alembic history        # see all revisions
docker compose exec api alembic current        # see DB state
docker compose exec api alembic stamp head     # mark as current (use carefully)
```

### Migration silently does nothing

Postgres container isn't ready when alembic runs. Add readiness check:
```bash
until docker compose exec -T db pg_isready -U $POSTGRES_USER; do sleep 1; done
docker compose exec -T api alembic upgrade head
```

### Column already exists error on re-deploy

Migration is not idempotent. Use `IF NOT EXISTS` in raw SQL migrations or check `op.get_context().dialect.name` in Alembic ops.

---

## Related

- [[wiki/architecture/optical-dev-server-deploy|optical-dev-server-deploy]] — Apache patterns
- [[wiki/architecture/new-project-checklist|new-project-checklist]] — setup guide
- [[wiki/architecture/gcp-deployment-lb-timeout|gcp-deployment-lb-timeout]] — no WebSockets
- [[wiki/tech-patterns/azure-ad-msal-auth|azure-ad-msal-auth]] — Azure AD setup
- [[wiki/concepts/monorepo-deploy-script-pitfall|monorepo-deploy-script-pitfall]] — deploy.sh gotchas
