Deploy askLenny
One docker compose up starts all three containers. This guide covers prerequisites, configuration, and self-hosted AI options.
Prerequisites
Docker + Compose
Docker Engine 20.10+ or Docker Desktop. Docker Compose v2 (included in Docker Desktop and recent Engine installs). No Kubernetes needed.
Database credentials
A read-only account that can query INFORMATION_SCHEMA and run SELECT queries. askLenny never issues DML — SELECT on system views and your tables is sufficient.
AI API key (or local model)
A Google Gemini API key for the default setup. Alternatively, run Ollama or any OpenAI-compatible model locally — see the zero-egress section below.
Quick start
Step 1. Create a directory and initialise the required files.
mkdir -p ~/asklenny/data
touch ~/asklenny/connectors.yaml ~/asklenny/.envStep 2. Edit .env with your secrets.
# .env (never commit this file)
GEMINI_API_KEY=AIza...
PROD_DB_PASSWORD=your_database_password
REPORTING_DB_PASSWORD=another_passwordStep 3. Save the Compose file (or download it from GitHub) alongside your .env.
services:
frontend:
image: asklenny/frontend:latest
restart: unless-stopped
ports:
- "5173:5173" # the only externally-exposed port
environment:
VITE_API_URL: http://localhost:8000
app:
image: asklenny/app:latest
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000" # loopback-only; browser calls from frontend
volumes:
- ./connectors.yaml:/app/connectors.yaml
environment:
GEMINI_API_KEY: ${GEMINI_API_KEY}
PROD_DB_PASSWORD: ${PROD_DB_PASSWORD}
ENGINE_URL: http://engine:3030
depends_on:
- engine
engine:
image: asklenny/engine:latest
restart: unless-stopped
# no ports exposed — accessible only from app via internal network
volumes:
- ./data:/dataStep 4. Edit connectors.yaml — see the reference below.
Step 5. Start all three containers.
docker compose up -dStep 6. Open http://localhost:5173 in your browser.
connectors.yaml reference
Lists every database the Python app container should connect to. Mount it into the container at /app/connectors.yaml.
The password_env_var field holds the name of the environment variable — the actual password is passed via the .env file and never written anywhere else.
Supported engines
| Database | engine value | Default port |
|---|---|---|
| SQL Server | mssql | 1433 |
| Azure SQL | mssql | 1433 |
| PostgreSQL | postgresql | 5432 |
| MySQL | mysql | 3306 |
| Snowflake | snowflake | 443 |
| Oracle | oracle | 1521 |
connectors:
- id: "production_warehouse"
display_name: "Production Warehouse"
engine: "mssql"
server: "10.0.1.55"
port: 1433
username: "asklenny_reader"
password_env_var: "PROD_DB_PASSWORD"
database: "Analytics"
- id: "reporting_db"
display_name: "Reporting Database"
engine: "postgresql"
server: "postgres.internal"
port: 5432
username: "asklenny_ro"
password_env_var: "REPORTING_DB_PASSWORD"
database: "reports"Environment variables
All secrets and configuration are injected via environment variables. Store them in your .env file and never commit it.
| Variable |
|---|
| GEMINI_API_KEY |
| LLM_BASE_URL |
| LLM_MODEL |
| EMBEDDING_MODEL |
| ENGINE_URL |
| VITE_API_URL |
| DB_* |
First run walkthrough
Open the dashboard
Navigate to http://localhost:5173. The Schema Discovery view loads automatically.
Review discovered schema
askLenny reads INFORMATION_SCHEMA from every configured connector. Tables and columns appear with a "New" badge — meaning they exist in the database but are not yet in the graph.
Generate AI descriptions
Click the ✨ AI button on any table or column to generate a plain-English description. Accept it, edit it, or write your own.
Commit to graph
Click "Commit to Graph". The Python app sends each node and its embedding to the Rust engine, which writes them to the persistent volume. This runs once per schema — or again when your schema changes.
Ask a question
Switch to the Query tab. Type a natural-language question. askLenny returns the generated SQL and executes it — the data table appears directly in the dashboard.
Zero-egress deployment with a local LLM
Add Ollama (or any OpenAI-compatible server) to the Compose file and update the app container to point at it. The Python container will route all LLM calls over the internal Docker network — nothing leaves your perimeter.
# Add to docker-compose.yml for a fully on-prem LLM:
ollama:
image: ollama/ollama:latest
volumes:
- ./ollama:/root/.ollama
# Then update the app service environment:
# LLM_BASE_URL: http://ollama:11434/v1
# LLM_MODEL: llama3.1
# EMBEDDING_MODEL: nomic-embed-text
# (remove GEMINI_API_KEY)Compatible with: Ollama, LM Studio, vLLM, LocalAI, private Azure OpenAI, or any OpenAI-compatible inference server.
Common issues
Dashboard loads but shows no connectors
Verify connectors.yaml is mounted correctly in docker-compose.yml. Run: docker compose exec app cat /app/connectors.yaml
Cannot connect to database
Check that the server address is reachable from inside the app container (use the host's Docker network IP or hostname, not 127.0.0.1). Verify the password env var name matches exactly.
AI enrichment returns an error
Check docker compose logs app for the FastAPI error. Verify GEMINI_API_KEY is set in .env. If using LLM_BASE_URL, ensure the model server is running and reachable.
Query returns SQL but no results
The read-only database account needs SELECT permission on your tables — not just INFORMATION_SCHEMA. Check the account privileges.
Graph data lost after restart
Ensure the engine volume mount ./data:/data is present in docker-compose.yml. Without it, the .dat files live inside the container layer and are lost on restart.