nullish is open source, so you can run it on your own servers. This guide shows you how to proceed with Docker or Podman.
Architecture
Section titled “Architecture”| Service | Image | Role |
|---|---|---|
postgres | postgres:18 | The database. |
server | usenullish/server | The API. Also runs background jobs and renders PDFs. |
client | usenullish/client | The web app. |
caddy | caddy:2 | Reverse 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.
Requirements
Section titled “Requirements”- A server with Docker or Podman.
- A domain pointing to it, e.g.
nullish.example.com.
Set it up
Section titled “Set it up”Create a folder with a
.envfile:Terminal window NULLISH_DB_USERNAME=nullishNULLISH_DB_PASSWORD=a-long-random-password# openssl rand -base64 32NULLISH_AUTH_SECRET=NULLISH_AUTH_URL=https://nullish.example.com/apiNULLISH_AUTH_SIGN_UP=invite_onlyAdd a
docker-compose.yml:services:postgres:image: postgres:18restart: unless-stoppedenvironment:POSTGRES_USER: ${NULLISH_DB_USERNAME}POSTGRES_PASSWORD: ${NULLISH_DB_PASSWORD}POSTGRES_DB: nullishvolumes:- postgres-data:/var/lib/postgresqlhealthcheck:test: ["CMD-SHELL", "pg_isready -U ${NULLISH_DB_USERNAME} -d nullish"]interval: 5stimeout: 5sretries: 10server:image: usenullish/server:0.1.0restart: unless-stoppeddepends_on:postgres:condition: service_healthyenvironment:NULLISH_DB_HOST: postgresNULLISH_DB_PORT: 5432NULLISH_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:/dataclient:image: usenullish/client:0.1.0restart: unless-stoppeddepends_on:- serverenvironment:NULLISH_API_URL: http://server:3000caddy:image: caddy:2restart: unless-stoppedports:- "80:80"- "443:443"volumes:- ./Caddyfile:/etc/caddy/Caddyfile- caddy-data:/datavolumes:postgres-data:nullish-data:caddy-data:Add a
Caddyfile:nullish.example.com {handle_path /api/* {reverse_proxy server:3000}handle {reverse_proxy client:3000}}Start it:
Terminal window docker compose up -dOpen your domain and sign up. First sign-up wins, whatever the sign-up setting. Create your organization: you're its admin.
Configuration
Section titled “Configuration”Database
Section titled “Database”| Variable | Required | Description |
|---|---|---|
NULLISH_DB_HOST | yes | Database host. |
NULLISH_DB_PORT | yes | Database port. |
NULLISH_DB_USERNAME | yes | Database user. |
NULLISH_DB_PASSWORD | yes | Database password. |
NULLISH_DB_NAME | no | Database name. Default nullish. |
Authentication
Section titled “Authentication”| Variable | Required | Description |
|---|---|---|
NULLISH_AUTH_SECRET | yes | At least 32 characters. Generate it with openssl rand -base64 32. |
NULLISH_AUTH_URL | yes | Public URL of the API. |
NULLISH_AUTH_SIGN_UP | no | true (anyone), false (nobody) or invite_only (with an invitation to join an organization). Default invite_only. |
Enable the assistant
Section titled “Enable the assistant”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:
NULLISH_LLM_URL=http://ollama:11434/v1NULLISH_LLM_CHAT_MODEL=qwen3.5:4bNULLISH_LLM_API_KEY=ollamaPass 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.
| Variable | Required | Description |
|---|---|---|
NULLISH_LLM_URL | for the assistant | Base URL of the endpoint, e.g. http://ollama:11434/v1. |
NULLISH_LLM_CHAT_MODEL | for the assistant | Model name. |
NULLISH_LLM_API_KEY | for the assistant | API key. Must not be empty, even for Ollama. |
NULLISH_LLM_CHAT_TOKEN_LIMIT | no | Tokens each user can use per period. Unlimited if unset. |
NULLISH_LLM_CHAT_TOKEN_LIMIT_PERIOD | no | hourly, daily, weekly or monthly. Default weekly. |
Operate it
Section titled “Operate it”- Updates: change the version of both images in
docker-compose.yml, thendocker compose pull && docker compose up -d. Migrations run on start. - Backups: back up the database, e.g. with
pg_dump. - Health:
GET /api/healthreturns{"status":"ok"}. - Logs:
docker compose logs -f server.
Command line
Section titled “Command line”The server image includes a small CLI:
# List users and their organizationsdocker compose exec server ./cli user list
# Permanently delete the organizations marked as deleteddocker compose exec server ./cli organization delete --dry-runOrganizations marked as deleted are only permanently deleted when you run organization delete.