14 KiB
Ghostfolio AI Agent — Setup Guide
For partner setup. Copy this, follow steps, run locally + VPS.
Quick Decision Tree (READ THIS FIRST!)
Before starting, check what's running:
docker ps | grep postgres
If you see gf-postgres-dev:
- You have existing containers with data
- → Skip to "Option A: Use Existing Containers"
- → No need for docker-compose
- → Fast start, your data is already there
If you see nothing (or only ghostfolio-db):
- You need fresh containers
- → Follow "Option B: Fresh Setup" below
- → One-time setup, then data persists
This prevents:
- ❌ Long container spin-ups
- ❌ Losing data by switching databases
- ❌ Needing to sign up repeatedly
One-Shot Quick Start
After cloning and editing .env:
# 1. Install dependencies
pnpm install
# 2. Start services (PostgreSQL + Redis)
docker-compose up -d
# 3. Run database migrations
pnpm nx run api:prisma:migrate
# 4. Start server
pnpm start:server
# 5. In another terminal, create account and get token:
# Open http://localhost:4200, sign up, then:
export GHOSTFOLIO_TOKEN="paste-token-from-browser-devtools"
# 6. Test AI endpoint
curl -X POST http://localhost:3333/api/v1/ai/chat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GHOSTFOLIO_TOKEN" \
-d '{"query": "Show my portfolio", "sessionId": "test"}'
Important: Two Container Options
READ THIS FIRST — You may have existing Ghostfolio containers running.
Check what's running:
docker ps | grep postgres
If you see gf-postgres-dev:
- You have OLD containers with your data
- Skip to "Option A: Use Existing Containers" below
If you see no postgres containers:
- Use "Option B: Fresh Setup with docker-compose"
Option A: Use Existing Containers (If Already Running)
IF you already have gf-postgres-dev and gf-redis-dev running:
# Don't run docker-compose up -d
# Just start the app
pnpm start
# Your existing account and data should work
Why: Your old containers already have your user account and holdings.
Option B: Fresh Setup with docker-compose
IF you want a fresh start or don't have containers yet:
Follow the steps below.
Local Setup (5 min)
1. Clone & Install
# Clone repo
git clone https://github.com/ghostfolio/ghostfolio.git
cd ghostfolio
# Install dependencies
pnpm install
2. Environment Variables
Create .env file in root:
# Database
DATABASE_URL="postgresql://ghostfolio:password@localhost:5432/ghostfolio"
# Redis (for AI agent memory)
REDIS_HOST=localhost
REDIS_PORT=6379
# OpenRouter (AI LLM provider)
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=anthropic/claude-3.5-sonnet
# JWT Secrets (generate random strings)
ACCESS_TOKEN_SALT=your-random-salt-string-here
JWT_SECRET_KEY=your-random-jwt-secret-here
# Optional: Supabase (if using)
SUPABASE_URL=your-supabase-url
SUPABASE_ANON_KEY=your-anon-key
Generate random secrets:
# Generate ACCESS_TOKEN_SALT
openssl rand -hex 32
# Generate JWT_SECRET_KEY
openssl rand -hex 32
3. Start Docker Services
# Start PostgreSQL + Redis
docker-compose up -d
# Or individual containers:
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=password -e POSTGRES_USER=ghostfolio -e POSTGRES_DB=ghostfolio postgres:16
docker run -d -p 6379:6379 redis:alpine
4. Get Authentication Token
The AI endpoint requires a JWT token. Get it by:
Option A: Web UI (Recommended)
- Open http://localhost:4200 in browser
- Sign up for a new account
- Open DevTools → Application → Local Storage
- Copy the
accessTokenvalue
Option B: API Call
# Sign up and get token
curl -X POST http://localhost:3333/api/v1/auth/anonymous \
-H "Content-Type: application/json" \
-d '{"accessToken": "any-string"}'
Save this token as GHOSTFOLIO_TOKEN in your shell:
export GHOSTFOLIO_TOKEN="your-jwt-token-here"
5. Run Project
# Start API server
pnpm start:server
# Or run all services
pnpm start
6. Test AI Agent
# Run AI tests
pnpm test:ai
# Run MVP evals
pnpm test:mvp-eval
VPS Setup (Hostinger) — External Services
What Goes on VPS
- Redis — AI agent session memory
- PostgreSQL — Optional (can use local)
- LangSmith — Observability (optional, for tracing)
Hostinger VPS Steps
1. SSH into VPS
ssh root@your-vps-ip
2. Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
3. Deploy Redis
docker run -d \
--name ghostfolio-redis \
-p 6379:6379 \
redis:alpine
4. Deploy PostgreSQL (Optional)
docker run -d \
--name ghostfolio-db \
-p 5432:5432 \
-e POSTGRES_PASSWORD=your-secure-password \
-e POSTGRES_USER=ghostfolio \
-e POSTGRES_DB=ghostfolio \
postgres:16
5. Firewall Rules
# Allow Redis (restrict to your IP)
ufw allow from YOUR_IP_ADDRESS to any port 6379
# Allow PostgreSQL (restrict to your IP)
ufw allow from YOUR_IP_ADDRESS to any port 5432
Update Local .env for VPS
# Use VPS services
REDIS_HOST=your-vps-ip
REDIS_PORT=6379
DATABASE_URL="postgresql://ghostfolio:your-secure-password@your-vps-ip:5432/ghostfolio"
# Keep local
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=anthropic/claude-3.5-sonnet
Run AI Agent Locally
Start Services
# Terminal 1: Docker services (if using local)
docker-compose up -d
# Terminal 2: API server
pnpm start:server
Test Chat Endpoint
# Using env variable (after export GHOSTFOLIO_TOKEN)
curl -X POST http://localhost:3333/api/v1/ai/chat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GHOSTFOLIO_TOKEN" \
-d '{
"query": "Analyze my portfolio risk",
"sessionId": "test-session-1"
}'
# Or paste token directly
curl -X POST http://localhost:3333/api/v1/ai/chat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"query": "What is my portfolio allocation?",
"sessionId": "test-session-2"
}'
Docker Compose (All-in-One)
Save as docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:16
container_name: ghostfolio-db
environment:
POSTGRES_USER: ghostfolio
POSTGRES_PASSWORD: password
POSTGRES_DB: ghostfolio
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:alpine
container_name: ghostfolio-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
postgres-data:
redis-data:
Run:
docker-compose up -d
Troubleshooting
Redis Connection Failed
# Check if Redis is running
docker ps | grep redis
# View logs
docker logs ghostfolio-redis
# Test connection
redis-cli -h localhost ping
Database Migration Failed
# Run migrations manually
pnpm nx run api:prisma:migrate
API Key Errors
# Verify OpenRouter key
curl https://openrouter.ai/api/v1/auth/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
Project Structure (AI Agent)
apps/api/src/app/endpoints/ai/
├── ai.controller.ts # POST /chat endpoint
├── ai.service.ts # Main orchestrator
├── ai-agent.chat.helpers.ts # Tool runners
├── ai-agent.utils.ts # Tool planning
├── ai-chat.dto.ts # Request validation
├── evals/ # Evaluation framework
└── *.spec.ts # Tests
Quick Commands Reference
# Install
pnpm install
# Start services
docker-compose up -d
# Run API
pnpm start:server
# Run tests
pnpm test:ai
pnpm test:mvp-eval
# Stop services
docker-compose down
Seed Money Runbook (Local / VPS / Railway)
Use this section to add portfolio activities quickly for demos and AI testing.
If activities exist but cash shows 0.00, add account balance snapshots (Ghostfolio reads cash from AccountBalance).
Local
# 1) Seed baseline AI MVP dataset
npm run database:seed:ai-mvp
# 2) Add extra money/orders dataset (idempotent)
npx dotenv-cli -e .env -- psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f tools/seed/seed-money.sql
VPS
# Run from project root on the VPS with env loaded
npm run database:migrate
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f tools/seed/seed-money.sql
Railway
# Link project/service once
railway link
railway service link ghostfolio-api
# Seed money dataset into Railway Postgres
tools/railway/seed-money.sh
# Optional health check after seeding
curl -sS https://ghostfolio-api-production.up.railway.app/api/v1/health
Notes:
tools/seed/seed-money.sqlis idempotent and usesrailway-seed:*markers.tools/railway/seed-money.shuploads SQL and executes it inside the Railwaypostgresservice.- Railway Redis default often uses no password auth. Keep
REDIS_PASSWORDempty onghostfolio-apiunless Redis auth is enabled.
No Repo Access: Copy/Paste Cash Top-Up SQL
Use this when only CLI/DB access is available.
WITH target_balances AS (
SELECT
a."id" AS account_id,
a."userId" AS user_id,
CASE
WHEN a."name" = 'MVP Portfolio' THEN 10000::double precision
WHEN a."name" = 'Income Portfolio' THEN 5000::double precision
WHEN a."name" = 'My Account' THEN 2000::double precision
ELSE NULL
END AS value
FROM "Account" a
WHERE a."name" IN ('MVP Portfolio', 'Income Portfolio', 'My Account')
)
INSERT INTO "AccountBalance" ("id", "accountId", "userId", "date", "value", "createdAt", "updatedAt")
SELECT
gen_random_uuid()::text,
t.account_id,
t.user_id,
CURRENT_DATE,
t.value,
now(),
now()
FROM target_balances t
WHERE t.value IS NOT NULL
ON CONFLICT ("accountId", "date")
DO UPDATE SET
"value" = EXCLUDED."value",
"updatedAt" = now();
Railway one-liner with inline SQL:
railway ssh -s postgres -- sh -lc 'cat >/tmp/topup.sql <<'"'"'"'"'"'"'"'"'SQL'"'"'"'"'"'"'"'"'
WITH target_balances AS (
SELECT
a."id" AS account_id,
a."userId" AS user_id,
CASE
WHEN a."name" = $$MVP Portfolio$$ THEN 10000::double precision
WHEN a."name" = $$Income Portfolio$$ THEN 5000::double precision
WHEN a."name" = $$My Account$$ THEN 2000::double precision
ELSE NULL
END AS value
FROM "Account" a
WHERE a."name" IN ($$MVP Portfolio$$, $$Income Portfolio$$, $$My Account$$)
)
INSERT INTO "AccountBalance" ("id", "accountId", "userId", "date", "value", "createdAt", "updatedAt")
SELECT gen_random_uuid()::text, t.account_id, t.user_id, CURRENT_DATE, t.value, now(), now()
FROM target_balances t
WHERE t.value IS NOT NULL
ON CONFLICT ("accountId", "date")
DO UPDATE SET "value" = EXCLUDED."value", "updatedAt" = now();
SQL
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f /tmp/topup.sql'
Next Steps
- ✅ Set up local environment
- ✅ Run
pnpm test:aito verify - ✅ Deploy to Railway (5 min) or Hostinger VPS (1-2 hours)
- 🔄 See
docs/DEPLOYMENT.mdfor full deployment guide - 🔄 Update MVP-VERIFICATION.md with deployed URL
Why Do I Need To Sign Up Each Time?
Problem: If you keep needing to sign up, you're switching between databases.
Cause: You have TWO sets of possible containers:
| Old Containers | New Containers (docker-compose.yml) |
|---|---|
gf-postgres-dev |
ghostfolio-db |
gf-redis-dev |
ghostfolio-redis |
Each has its own database. When you switch between them, you get a fresh database.
Solution: Pick ONE and use it consistently.
Option A: Keep using old containers
# Don't run docker-compose
# Just:
pnpm start
Option B: Switch to new containers
# Stop old ones
docker stop gf-postgres-dev gf-redis-dev
# Start new ones
docker-compose up -d
# Migrate
pnpm nx run api:prisma:migrate
# Create account ONCE
# Data persists from now on
Data Persistence:
- ✅ User accounts persist in Docker volumes
- ✅ Holdings persist
- ✅ No need to re-sign up if using same containers
For full details: See docs/DATA-PERSISTENCE.md
Deployment
Quick options:
| Platform | Time | Cost | Guide |
|---|---|---|---|
| Railway | 5 min | Free tier | railway.toml included |
| Hostinger VPS | 1-2 hours | Already paid | See docs/DEPLOYMENT.md |
Railway quick start:
# 1. Push to GitHub
git add . && git commit -m "Ready for Railway" && git push
# 2. Go to https://railway.app/new → Connect GitHub repo
# 3. Add env vars in Railway dashboard:
# API_KEY_OPENROUTER=sk-or-v1-...
# OPENROUTER_MODEL=anthropic/claude-3.5-sonnet
# JWT_SECRET_KEY=(openssl rand -hex 32)
# ACCESS_TOKEN_SALT=(openssl rand -hex 32)
# REDIS_PASSWORD=(leave empty unless Redis auth is enabled)
# 4. Deploy → Get URL like:
# https://your-app.up.railway.app
Full deployment guide: docs/DEPLOYMENT.md
Speed Up Docker Builds
Use these commands for faster iteration loops:
# 1) Build with BuildKit enabled
DOCKER_BUILDKIT=1 docker build -t ghostfolio:dev .
# 2) Warm dependency layer first (runs fast when package-lock.json is unchanged)
docker build --target builder -t ghostfolio:builder-cache .
# 3) Deploy in detached mode on Railway to keep terminal free
railway up --detach --service ghostfolio-api
# 4) Build with explicit local cache reuse
docker buildx build \
--cache-from type=local,src=.buildx-cache \
--cache-to type=local,dest=.buildx-cache-new,mode=max \
-t ghostfolio:dev .
mv .buildx-cache-new .buildx-cache
High-impact optimization path:
- Keep
package-lock.jsonstable to maximize Docker cache hits. - Group dependency changes into fewer commits.
- Use prebuilt image deployment for Railway when push frequency is high.
Questions?
- OpenRouter key: https://openrouter.ai/keys
- Railway: https://railway.app
- Ghostfolio docs: https://ghostfolio.org/docs
- Hostinger VPS: https://support.hostinger.com/en/articles/4983461-how-to-connect-to-vps-using-ssh
- Full deployment docs:
docs/DEPLOYMENT.md