Deployment

Deploy askLenny

One docker compose up starts all three containers. This guide covers prerequisites, configuration, and self-hosted AI options.

Prerequisites

01

Docker + Compose

Docker Engine 20.10+ or Docker Desktop. Docker Compose v2 (included in Docker Desktop and recent Engine installs). No Kubernetes needed.

02

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.

03

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.

terminal
mkdir -p ~/asklenny/data
touch ~/asklenny/connectors.yaml ~/asklenny/.env

Step 2. Edit .env with your secrets.

.env
# .env  (never commit this file)
GEMINI_API_KEY=AIza...
PROD_DB_PASSWORD=your_database_password
REPORTING_DB_PASSWORD=another_password

Step 3. Save the Compose file (or download it from GitHub) alongside your .env.

docker-compose.yml
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:/data

Step 4. Edit connectors.yaml — see the reference below.

Step 5. Start all three containers.

terminal
docker compose up -d

Step 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

Databaseengine valueDefault port
SQL Servermssql1433
Azure SQLmssql1433
PostgreSQLpostgresql5432
MySQLmysql3306
Snowflakesnowflake443
Oracleoracle1521
connectors.yaml
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

1

Open the dashboard

Navigate to http://localhost:5173. The Schema Discovery view loads automatically.

2

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.

3

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.

4

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.

5

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.

docker-compose.yml (additions)
  # 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.