innercontext/docs/DEPLOYMENT.md
Piotr Oleszczyk d62812274b fix(docs): correct Debian 13 deployment steps
- Switch to Node.js 24 LTS via nvm
- Install uv to /usr/local/bin via UV_INSTALL_DIR for system-wide access
- Install pnpm as standalone binary from GitHub releases (not corepack
  shim which breaks when copied out of its nvm directory)
- Add libpq5 to apt deps (psycopg3 requires libpq at runtime)
- Add GEMINI_API_KEY and GEMINI_MODEL to backend .env template
- Add ORIGIN to frontend .env.production (SvelteKit CSRF protection)

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

350 lines
8.6 KiB
Markdown

# 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):
```bash
# 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
```bash
pct enter 200 # or SSH into the container
```
### System packages
```bash
apt update && apt upgrade -y
apt install -y git nginx curl ca-certificates gnupg lsb-release libpq5
```
### Python 3.12+ + uv
```bash
apt install -y python3 python3-venv
curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh
```
Installing to `/usr/local/bin` makes `uv` available system-wide (required for `sudo -u innercontext uv sync`).
### Node.js 24 LTS + pnpm
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
. "$HOME/.nvm/nvm.sh"
nvm install 24
```
Copy `node` to `/usr/local/bin` so it is accessible system-wide
(required for `sudo -u innercontext` and for systemd).
Symlinking into `/root/.nvm/` won't work — other users can't traverse `/root/`.
Use `--remove-destination` to replace any existing symlink with a real file:
```bash
cp --remove-destination "$(nvm which current)" /usr/local/bin/node
```
Install pnpm as a standalone binary from GitHub releases — self-contained,
no wrapper scripts, works system-wide. Do **not** use `corepack enable pnpm`
(the shim requires its nvm directory structure and breaks when copied/linked):
```bash
curl -fsSL "https://github.com/pnpm/pnpm/releases/latest/download/pnpm-linux-x64" \
-o /usr/local/bin/pnpm
chmod 755 /usr/local/bin/pnpm
```
### Application user
```bash
useradd --system --create-home --shell /bin/bash innercontext
```
---
## 4. Create the database on the PostgreSQL LXC
Run on the **PostgreSQL LXC**:
```bash
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:
```bash
systemctl reload postgresql
```
---
## 5. Clone the repository
```bash
mkdir -p /opt/innercontext
git clone https://github.com/your-user/innercontext.git /opt/innercontext
chown -R innercontext:innercontext /opt/innercontext
```
---
## 6. Backend setup
```bash
cd /opt/innercontext/backend
```
### Install dependencies
```bash
sudo -u innercontext uv sync
```
### Create `.env`
```bash
cat > /opt/innercontext/backend/.env <<'EOF'
DATABASE_URL=postgresql+psycopg://innercontext:change-me@<pg-lxc-ip>/innercontext
GEMINI_API_KEY=your-gemini-api-key
# GEMINI_MODEL=gemini-flash-latest # optional, this is the default
EOF
chmod 600 /opt/innercontext/backend/.env
chown innercontext:innercontext /opt/innercontext/backend/.env
```
### Run database migrations
```bash
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
```bash
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
```bash
cp /opt/innercontext/systemd/innercontext.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now innercontext
systemctl status innercontext
```
---
## 7. Frontend build and setup
```bash
cd /opt/innercontext/frontend
```
### Create `.env.production`
```bash
cat > /opt/innercontext/frontend/.env.production <<'EOF'
PUBLIC_API_BASE=http://innercontext.lan/api
ORIGIN=http://innercontext.lan
EOF
chmod 600 /opt/innercontext/frontend/.env.production
chown innercontext:innercontext /opt/innercontext/frontend/.env.production
```
### Install dependencies and build
```bash
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
```bash
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
```bash
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:
```nginx
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
```bash
# 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
```bash
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/*`
```bash
systemctl status innercontext
journalctl -u innercontext -n 50
# Check .env DATABASE_URL is correct and PG LXC accepts connections
```
### 502 Bad Gateway on `/`
```bash
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
```bash
# 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
```bash
# 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
```