innercontext/docs/DEPLOYMENT.md
Piotr Oleszczyk 3c1dcbeb06 feat(backend): add Alembic migrations
- Add alembic 1.14 to dependencies (uv sync → 1.18.4 installed)
- Configure alembic/env.py: loads DATABASE_URL from env, imports all
  SQLModel models so metadata is fully populated for autogenerate
- Generate initial migration (c2d626a2b36c) covering all 9 tables:
  products, product_inventory, medication_entries, medication_usages,
  lab_results, routines, routine_steps, grooming_schedule,
  skin_condition_snapshots — with all indexes and constraints
- Add ExecStartPre to innercontext.service: runs alembic upgrade head
  before uvicorn starts (idempotent, safe on every restart)
- Update DEPLOYMENT.md: add migration step to backend setup and update
  flow; document alembic stamp head for existing installations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 20:14:57 +01:00

7.6 KiB

Deployment guide — Proxmox LXC (home network)

Target architecture:

Reverse proxy (existing)             innercontext LXC (new, Debian 13)
┌──────────────────────┐             ┌────────────────────────────────────┐
│ reverse proxy        │────────────▶│ nginx :80                          │
│ innercontext.lan → * │             │   /api/*  → uvicorn :8000/*        │
└──────────────────────┘             │   /mcp/*  → uvicorn :8000/mcp/*    │
                                     │   /*      → SvelteKit Node :3000   │
                                     └────────────────────────────────────┘
                                             │                │
                                      FastAPI + MCP    SvelteKit Node

1. Prerequisites

  • Proxmox VE host with an existing PostgreSQL LXC and a reverse proxy
  • LAN hostname innercontext.lan resolvable on the network (via router DNS or /etc/hosts)
  • The PostgreSQL LXC must accept connections from the innercontext LXC IP

2. Create the LXC container

In the Proxmox UI (or via CLI):

# CLI example — adjust storage, bridge, IP to your environment
pct create 200 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
  --hostname innercontext \
  --cores 2 \
  --memory 1024 \
  --swap 512 \
  --rootfs local-lvm:8 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --unprivileged 1 \
  --start 1

Note the container's IP address after it starts (pct exec 200 -- ip -4 a).


3. Container setup

pct enter 200   # or SSH into the container

System packages

apt update && apt upgrade -y
apt install -y git nginx curl ca-certificates gnupg lsb-release

Python 3.12+ + uv

apt install -y python3 python3-venv
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env   # or re-login

Node.js 22 + pnpm

curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
npm install -g pnpm

Application user

useradd --system --create-home --shell /bin/bash innercontext

4. Create the database on the PostgreSQL LXC

Run on the PostgreSQL LXC:

psql -U postgres <<'SQL'
CREATE USER innercontext WITH PASSWORD 'change-me';
CREATE DATABASE innercontext OWNER innercontext;
SQL

Edit /etc/postgresql/18/main/pg_hba.conf and add (replace <lxc-ip> with the innercontext container IP):

host  innercontext  innercontext  <lxc-ip>/32  scram-sha-256

Then reload:

systemctl reload postgresql

5. Clone the repository

mkdir -p /opt/innercontext
git clone https://github.com/your-user/innercontext.git /opt/innercontext
chown -R innercontext:innercontext /opt/innercontext

6. Backend setup

cd /opt/innercontext/backend

Install dependencies

sudo -u innercontext uv sync

Create .env

cat > /opt/innercontext/backend/.env <<'EOF'
DATABASE_URL=postgresql+psycopg://innercontext:change-me@<pg-lxc-ip>/innercontext
EOF
chmod 600 /opt/innercontext/backend/.env
chown innercontext:innercontext /opt/innercontext/backend/.env

Run database migrations

sudo -u innercontext bash -c '
  cd /opt/innercontext/backend
  uv run alembic upgrade head
'

This creates all tables on first run. On subsequent deploys it applies only the new migrations.

Existing database (tables already created by create_db_and_tables): Run uv run alembic stamp head instead to mark the current schema as migrated without re-running DDL.

Test

sudo -u innercontext bash -c '
  cd /opt/innercontext/backend
  uv run uvicorn main:app --host 127.0.0.1 --port 8000
'
# Ctrl-C after confirming it starts

Install systemd service

cp /opt/innercontext/systemd/innercontext.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now innercontext
systemctl status innercontext

7. Frontend build and setup

cd /opt/innercontext/frontend

Create .env.production

cat > /opt/innercontext/frontend/.env.production <<'EOF'
PUBLIC_API_BASE=http://innercontext.lan/api
EOF
chmod 600 /opt/innercontext/frontend/.env.production
chown innercontext:innercontext /opt/innercontext/frontend/.env.production

Install dependencies and build

sudo -u innercontext bash -c '
  cd /opt/innercontext/frontend
  pnpm install
  PUBLIC_API_BASE=http://innercontext.lan/api pnpm build
'

The production build lands in /opt/innercontext/frontend/build/.

Install systemd service

cp /opt/innercontext/systemd/innercontext-node.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now innercontext-node
systemctl status innercontext-node

8. nginx setup

cp /opt/innercontext/nginx/innercontext.conf /etc/nginx/sites-available/innercontext
ln -s /etc/nginx/sites-available/innercontext /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx

9. Reverse proxy configuration

Point your existing reverse proxy at the innercontext LXC's nginx (<innercontext-lxc-ip>:80).

Example — Caddy:

innercontext.lan {
    reverse_proxy <innercontext-lxc-ip>:80
}

Example — nginx upstream:

server {
    listen 80;
    server_name innercontext.lan;
    location / {
        proxy_pass http://<innercontext-lxc-ip>:80;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Reload your reverse proxy after applying the change.


10. Verification

# From any machine on the LAN:
curl http://innercontext.lan/api/health-check    # {"status":"ok"}
curl http://innercontext.lan/api/products        # []
curl http://innercontext.lan/                    # SvelteKit HTML shell
curl -N http://innercontext.lan/mcp/mcp          # MCP StreamableHTTP endpoint

The web UI should be accessible at http://innercontext.lan.


11. Updating the application

cd /opt/innercontext
git pull

# Sync backend dependencies if pyproject.toml changed:
cd backend && sudo -u innercontext uv sync && cd ..

# Apply any new DB migrations (runs automatically via ExecStartPre, but safe to run manually first):
sudo -u innercontext bash -c 'cd /opt/innercontext/backend && uv run alembic upgrade head'

# Rebuild frontend:
cd frontend && sudo -u innercontext bash -c '
  pnpm install
  PUBLIC_API_BASE=http://innercontext.lan/api pnpm build
'

systemctl restart innercontext innercontext-node

12. Troubleshooting

502 Bad Gateway on /api/*

systemctl status innercontext
journalctl -u innercontext -n 50
# Check .env DATABASE_URL is correct and PG LXC accepts connections

502 Bad Gateway on /

systemctl status innercontext-node
journalctl -u innercontext-node -n 50
# Verify /opt/innercontext/frontend/build/index.js exists (pnpm build ran successfully)

MCP endpoint not responding

# MCP uses SSE — disable buffering is already in nginx config
# Verify the backend started successfully:
curl http://127.0.0.1:8000/health-check
# Check FastAPI logs:
journalctl -u innercontext -n 50

Database connection refused

# From innercontext LXC:
psql postgresql+psycopg://innercontext:change-me@<pg-lxc-ip>/innercontext -c "SELECT 1"
# If it fails, check pg_hba.conf on the PG LXC and verify the IP matches