Skip to content

Self-hosting

nullish is open source, so you can run it on your own servers. This guide shows you how to proceed with Docker or Podman.

ServiceImageRole
postgrespostgres:18The database.
serverusenullish/serverThe API. Also runs background jobs and renders PDFs.
clientusenullish/clientThe web app.
caddycaddy:2Reverse proxy: HTTPS, and /api to the server.

Files like generated PDFs are kept temporarily in the server's /data volume and deleted periodically. Everything else lives in the database.

Images are published on Docker Hub for linux/amd64 and linux/arm64, tagged latest and by version, e.g. 0.1.0, 0.1 or 0. Pin a version so updates happen when you choose.

  • A server with Docker or Podman.
  • A domain pointing to it, e.g. nullish.example.com.
  1. Create a folder with a .env file:

    Terminal window
    NULLISH_DB_USERNAME=nullish
    NULLISH_DB_PASSWORD=a-long-random-password
    # openssl rand -base64 32
    NULLISH_AUTH_SECRET=
    NULLISH_AUTH_URL=https://nullish.example.com/api
    NULLISH_AUTH_SIGN_UP=invite_only
  2. Add a docker-compose.yml:

    services:
    postgres:
    image: postgres:18
    restart: unless-stopped
    environment:
    POSTGRES_USER: ${NULLISH_DB_USERNAME}
    POSTGRES_PASSWORD: ${NULLISH_DB_PASSWORD}
    POSTGRES_DB: nullish
    volumes:
    - postgres-data:/var/lib/postgresql
    healthcheck:
    test: ["CMD-SHELL", "pg_isready -U ${NULLISH_DB_USERNAME} -d nullish"]
    interval: 5s
    timeout: 5s
    retries: 10
    server:
    image: usenullish/server:0.1.0
    restart: unless-stopped
    depends_on:
    postgres:
    condition: service_healthy
    environment:
    NULLISH_DB_HOST: postgres
    NULLISH_DB_PORT: 5432
    NULLISH_DB_USERNAME: ${NULLISH_DB_USERNAME}
    NULLISH_DB_PASSWORD: ${NULLISH_DB_PASSWORD}
    NULLISH_AUTH_SECRET: ${NULLISH_AUTH_SECRET}
    NULLISH_AUTH_URL: ${NULLISH_AUTH_URL}
    NULLISH_AUTH_SIGN_UP: ${NULLISH_AUTH_SIGN_UP}
    volumes:
    - nullish-data:/data
    client:
    image: usenullish/client:0.1.0
    restart: unless-stopped
    depends_on:
    - server
    environment:
    NULLISH_API_URL: http://server:3000
    caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
    - "80:80"
    - "443:443"
    volumes:
    - ./Caddyfile:/etc/caddy/Caddyfile
    - caddy-data:/data
    volumes:
    postgres-data:
    nullish-data:
    caddy-data:
  3. Add a Caddyfile:

    nullish.example.com {
    handle_path /api/* {
    reverse_proxy server:3000
    }
    handle {
    reverse_proxy client:3000
    }
    }
  4. Start it:

    Terminal window
    docker compose up -d
  5. Open your domain and sign up. First sign-up wins, whatever the sign-up setting. Create your organization: you're its admin.

VariableRequiredDescription
NULLISH_DB_HOSTyesDatabase host.
NULLISH_DB_PORTyesDatabase port.
NULLISH_DB_USERNAMEyesDatabase user.
NULLISH_DB_PASSWORDyesDatabase password.
NULLISH_DB_NAMEnoDatabase name. Default nullish.
VariableRequiredDescription
NULLISH_AUTH_SECRETyesAt least 32 characters. Generate it with openssl rand -base64 32.
NULLISH_AUTH_URLyesPublic URL of the API.
NULLISH_AUTH_SIGN_UPnotrue (anyone), false (nobody) or invite_only (with an invitation to join an organization). Default invite_only.

The assistant needs an LLM. Any OpenAI-compatible endpoint works, e.g. your own model with Ollama or a provider of your choice. Without one, nullish works fine, but the assistant is disabled.

Add the settings to your .env:

Terminal window
NULLISH_LLM_URL=http://ollama:11434/v1
NULLISH_LLM_CHAT_MODEL=qwen3.5:4b
NULLISH_LLM_API_KEY=ollama

Pass them to the server in docker-compose.yml:

server:
environment:
NULLISH_LLM_URL: ${NULLISH_LLM_URL}
NULLISH_LLM_CHAT_MODEL: ${NULLISH_LLM_CHAT_MODEL}
NULLISH_LLM_API_KEY: ${NULLISH_LLM_API_KEY}

Then restart with docker compose up -d. The assistant is enabled once the first three settings below are set.

VariableRequiredDescription
NULLISH_LLM_URLfor the assistantBase URL of the endpoint, e.g. http://ollama:11434/v1.
NULLISH_LLM_CHAT_MODELfor the assistantModel name.
NULLISH_LLM_API_KEYfor the assistantAPI key. Must not be empty, even for Ollama.
NULLISH_LLM_CHAT_TOKEN_LIMITnoTokens each user can use per period. Unlimited if unset.
NULLISH_LLM_CHAT_TOKEN_LIMIT_PERIODnohourly, daily, weekly or monthly. Default weekly.
  • Updates: change the version of both images in docker-compose.yml, then docker compose pull && docker compose up -d. Migrations run on start.
  • Backups: back up the database, e.g. with pg_dump.
  • Health: GET /api/health returns {"status":"ok"}.
  • Logs: docker compose logs -f server.

The server image includes a small CLI:

Terminal window
# List users and their organizations
docker compose exec server ./cli user list
# Permanently delete the organizations marked as deleted
docker compose exec server ./cli organization delete --dry-run

Organizations marked as deleted are only permanently deleted when you run organization delete.