entrytwo

docs / getting-started / installation.md

Installation

This guide gets a self-hosted entrytwo instance running.

entrytwo is AI-native double-entry accounting with a Git-style data model. The AI posts real journal entries when confident. Full history, branches, blame. Self-hosted.


Deploy: Most production setups use a VPS with Docker and HTTPS. You can also install and run locally (e.g. on a Mac) for real use: same full app, like classic non-cloud accounting software. Runs on your VPS or locally.

Let AI install it for you

Not a terminal person? You can still get this running. Point an AI coding agent at this page (and the repo) and ask it to install entrytwo for you — for example Claude Code, OpenClaw, Hermes, Cursor, or a similar tool that can run commands on your machine or VPS. Share your goal (local Mac vs VPS + domain), paste errors back if something fails, and keep secrets (passwords, API keys) out of chats you do not trust. The steps below remain the source of truth; the agent just walks them with you. Canonical agent files: llms.txt and SKILL.md.

Prerequisites

Choose one of the two supported install paths:

Path A — Git / npm (clone + build locally)

  • Node.js 22.12 or later, on the latest 22.x (Prisma engines reject earlier 22.x; a generic >= 22 is not enough)
  • npm, Git, and openssl
  • PostgreSQL 16 (local or via Docker)
  • Optional but recommended: Docker & Docker Compose (for Postgres)

Install Node from the official nodejs.org download / version-manager page, then verify:

node --version   # expect v22.12.0 or a later 22.x (pinned: v22.23.4)

Without nvm: these commands install the official 22.23.4 binary into your home directory, verify its checksum, and add it to the current terminal's PATH. This matches the repository's exact .nvmrc pin; 22.12.0 is the minimum, not the recommended patch. Supports Debian/Ubuntu (glibc) x64/ARM64 and macOS Intel/Apple Silicon. Alpine/musl needs a different build. Requires curl, tar, and sha256sum (Linux) or shasum (macOS). Run before npm ci:

NODE_VERSION=22.23.4
case "$(uname -s)/$(uname -m)" in
  Linux/x86_64) NODE_PLATFORM=linux-x64 ;;
  Linux/aarch64|Linux/arm64) NODE_PLATFORM=linux-arm64 ;;
  Darwin/x86_64) NODE_PLATFORM=darwin-x64 ;;
  Darwin/arm64) NODE_PLATFORM=darwin-arm64 ;;
  *) echo "Unsupported platform; use nodejs.org/en/download" >&2; exit 1 ;;
esac
NODE_HOME="$HOME/.local/share/node-v$NODE_VERSION-$NODE_PLATFORM"
(
  set -eu
  NODE_TMP="$(mktemp -d)"
  trap 'rm -rf "$NODE_TMP"' EXIT
  cd "$NODE_TMP"
  ARCHIVE="node-v$NODE_VERSION-$NODE_PLATFORM.tar.gz"
  curl -fLO "https://nodejs.org/dist/v$NODE_VERSION/$ARCHIVE"
  curl -fLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt"
  grep "  $ARCHIVE$" SHASUMS256.txt > CHECKSUM
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum -c CHECKSUM
  else
    shasum -a 256 -c CHECKSUM
  fi
  mkdir -p "$NODE_HOME"
  tar -xzf "$ARCHIVE" --strip-components=1 -C "$NODE_HOME"
) && export PATH="$NODE_HOME/bin:$PATH" && node --version

For future terminals, add export PATH="<the installed NODE_HOME path>/bin:$PATH" to your own ~/.bashrc (Linux bash) or ~/.zshrc (macOS zsh), or repeat it manually. The commands do not edit shell profiles and do not need sudo. With nvm already installed, use nvm install && nvm use from the clone instead.

Path B — Docker Compose pull (pull published image, no clone)

  • Docker Engine or Docker Desktop with Docker Compose v2
  • curl and openssl for downloading installation files and generating secrets

Node.js, PostgreSQL, and Git are not required on the host for the Docker installation.

Optional but useful:

  • A domain + reverse proxy (Caddy, Nginx, Traefik) for HTTPS
  • SMTP for magic-link login (and invitation emails)
  • Google and/or Microsoft Entra ID OAuth apps for staff sign-in
  • LLM API key (OpenAI, Anthropic, xAI, etc.)

Quick Start (two supported paths)

Path A — Git / npm (clone + build locally)

Best for: developers, AI coding enthusiasts, customization.

Installation: clone, install dependencies, configure, run. Updates: back up, review and select a source revision, then rebuild/migrate. Customization: full source access. Provide a reachable PostgreSQL database separately. The bundled Compose database does not publish a host port, so it cannot serve a native Node.js app on the host without additional configuration.

# 1. Clone
git clone https://github.com/felixkrusch/entrytwo.git
cd entrytwo
node --version   # expect v22.12.0 or a later 22.x

# 2. Install dependencies (use the lockfile versions as-is)
npm ci

# 3. Configure environment
cp .env.example .env
# Generate each secret independently (do not reuse values; do not paste them into agent chats):
#   openssl rand -hex 32   # AUTH_SECRET
#   openssl rand -hex 32   # ENCRYPTION_KEY  (must be exactly 64 hex characters)
#   openssl rand -hex 32   # JOB_RUNNER_SECRET
# openssl rand -base64 48 is also valid for AUTH_SECRET and JOB_RUNNER_SECRET
# (≥ 32 characters), but use one convention: hex 32 for all of these.
# Example xAI chat config (use a model id available in your xAI account):
#   NEXT_LLM_PROVIDER=xai
#   NEXT_LLM_MODEL=<model id from your xAI console>
#   XAI_API_KEY=<your key>

# 4. Start PostgreSQL (choose one)

# Option A: local development container matching .env.example
docker run --name entrytwo-dev-db \
  -e POSTGRES_USER=entrytwo \
  -e POSTGRES_PASSWORD=entrytwo_dev_password \
  -e POSTGRES_DB=entrytwo \
  -p 127.0.0.1:5432:5432 \
  -v entrytwo_dev_postgres:/var/lib/postgresql/data \
  -d postgres:16-alpine

# Wait until the example container accepts connections (skip if using local Postgres)
i=0
until docker exec entrytwo-dev-db pg_isready -U entrytwo -d entrytwo; do
  i=$((i + 1))
  if [ "$i" -ge 30 ]; then
    echo "PostgreSQL did not become ready" >&2
    exit 1
  fi
  sleep 1
done

# Option B: Local Postgres
# Make sure you have a database called "entrytwo" and update DATABASE_URL accordingly.

# 5. Once PostgreSQL is ready, generate the client and apply committed migrations
npx prisma generate
npx prisma migrate deploy

# 6. Start the app
mkdir -p .data/files .data/backups
npm run dev

Open http://localhost:3000

If npm or Prisma prints an update or security banner after npm ci, keep the lockfile versions. Do not run npm audit fix or npm audit fix --force as an installation step.

The example database credentials are for local development only. Skip the example container if PostgreSQL already runs on port 5432; use your database's connection details in DATABASE_URL. Generate each secret separately with openssl rand -hex 32 (ENCRYPTION_KEY must be exactly 64 hex characters) and keep the original ENCRYPTION_KEY safe for restores. Complete company onboarding to initialize your chart of accounts.

Never seed a real installation. npx prisma db seed truncates application tables before adding sample data. It is only for disposable development databases, not installation or updates. Use prisma migrate dev only when developing schema changes, not for applying published migrations.

For background jobs, run node --env-file=.env scripts/job-worker.mjs in a second terminal. Actual invoice email delivery needs SMTP. Development instant email sign-in does not send real email and is disabled in production.


Path B — Docker / Compose (self-hosters, businesses, server administrators)

Best for: self-hosters, businesses, server administrators.

Installation: pull image, configure, start. Updates: pull new image + migrate/restart. Customization: usually configuration-based. Database and Redis: included in Compose.

# 1. Create a fresh directory
mkdir entrytwo && cd entrytwo

# 2. Download public installation files
RELEASE_URL=https://github.com/felixkrusch/entrytwo-releases/releases/latest/download
curl -fL "$RELEASE_URL/compose.yaml" -o compose.yaml
curl -fL "$RELEASE_URL/entrytwo.env.example" -o .env.example

# 3. Copy and edit environment. Never overwrite an existing installation's .env.
umask 077
cp .env.example .env
# Set at minimum:
#   POSTGRES_PASSWORD, AUTH_SECRET, ENCRYPTION_KEY, JOB_RUNNER_SECRET
#   AUTH_URL (public origin), AUTH_TRUST_HOST=true
#   SMTP/OAuth sign-in method (required in production)
#   At least one LLM key if using AI/chat features

# Generate separate values with openssl rand -hex 32 for each of:
# POSTGRES_PASSWORD, AUTH_SECRET, ENCRYPTION_KEY, JOB_RUNNER_SECRET, UPDATER_TOKEN
# ENCRYPTION_KEY must be exactly 64 hex characters. Do not reuse values across keys.
# openssl rand -base64 48 is also valid for AUTH_SECRET, JOB_RUNNER_SECRET, and
# UPDATER_TOKEN, but use one convention: hex 32 for all of these.
# Edit .env before continuing. Keep UPDATE_MANIFEST_URL and COSIGN_IDENTITY defaults.
# Do not paste generated secrets or API keys into agent chats.

# 4. Validate, pull and start, always from this installation directory
docker compose config --quiet
docker compose pull
docker compose up -d

# 5. Open your configured AUTH_URL (public deployments require HTTPS)
# Migrations run automatically. A separate worker handles background jobs.
# On first visit with no admin users, create the initial **ADMIN** account.

The app and updater are separate public images; neither requires a GitHub login. The host port binds to 127.0.0.1 by default. Use a host reverse proxy for public HTTPS. For a local HTTP-only lab, set AUTH_ALLOW_HTTP=true; never use that setting on a public server. The updater mounts the Docker socket and is privileged in practice. Do not expose its port or share its token. Docker containers run in production mode even on a laptop: real SMTP/OAuth and administrator TOTP are required. SMTP is also needed for invoice delivery even if OAuth handles sign-in. The application source is public at felixkrusch/entrytwo; it is not needed for this image-based installation.


Handler mode (one compose per client)

entrytwo is not multi-tenant. One running stack = one legal entity.

If you keep books for several companies (bookkeeper, agency, “handler”), give each client its own compose project:

  • Own directory (or at least own .env, volumes, and backups)
  • Own COMPOSE_PROJECT_NAME (e.g. entrytwo-acme)
  • Own loopback-bound host port if they share a machine (127.0.0.1:3000:3000, 127.0.0.1:3001:3000, …)
  • Own secrets (POSTGRES_PASSWORD, AUTH_SECRET, ENCRYPTION_KEY, …)
  • Own .etbackup files — never restore company A’s backup into company B

RAM: plan ≥ 2 GB per stack. Chat lock on for bulk agent work; branch before bulk import; never post below 0.85 confidence; a human closes the period.

One paid license covers installations for businesses you own. Unrelated client businesses need their own licenses. Public source access is governed by the proprietary source-available license, not an open-source license.


Environment Variables (Key Ones)

Copy .env.example and fill in:

Required (local or production)

  • DATABASE_URL
  • AUTH_SECRET (≥ 32 characters)
  • POSTGRES_PASSWORD (when using compose)
  • LICENSE_VERIFY_URL — https://entrytwo.com/api/license/verify (paid key activation; trial needs no key)

Required in production (app refuses to start / health stays degraded without these)

  • ENCRYPTION_KEY (64 hex chars)
  • JOB_RUNNER_SECRET
  • AUTH_URL (public origin, e.g. https://books.example.com)
  • AUTH_TRUST_HOST=true (behind nginx/caddy/traefik)
  • At least one real sign-in method:
    • SMTP: SMTP_HOST, SMTP_USER, SMTP_PASS (and ideally SMTP_FROM), and/or
    • Google: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET, and/or
    • Microsoft: MICROSOFT_ENTRA_ID_ID + MICROSOFT_ENTRA_ID_SECRET (+ issuer)

Required for the published Docker stack

  • UPDATER_TOKEN - shared secret for the updater sidecar

For AI features

  • LLM key + NEXT_LLM_PROVIDER / NEXT_LLM_MODEL
  • xAI example: NEXT_LLM_PROVIDER=xai, XAI_API_KEY, and NEXT_LLM_MODEL set to a model id available in your xAI account (copy it from the console; do not guess a version)

For production / updates

  • UPDATE_MANIFEST_URL - points at your signed release manifest
  • Cosign settings (COSIGN_IDENTITY, COSIGN_PUBLIC_KEY, etc.)
  • STRIPE_WEBHOOK_SECRET / PayPal secrets (if using processor feeds)

Full list and explanations are in the app .env.example. Production auth go-live: VPS-Auth-Install.md.


Manual Postgres (without Docker)

  1. Create the database:
CREATE USER entrytwo WITH PASSWORD 'entrytwo_dev_password';
CREATE DATABASE entrytwo OWNER entrytwo;
  1. Set in .env:
DATABASE_URL="postgresql://entrytwo:entrytwo_dev_password@localhost:5432/entrytwo"

Run the SQL as a PostgreSQL administrator. The password is a local-development example; use a fresh password for any real deployment. Making the application role the database owner grants it the schema permissions required for migrations.

  1. From the cloned repository, install dependencies and apply migrations:
npm ci
npx prisma generate
npx prisma migrate deploy
mkdir -p .data/files .data/backups
npm run dev

First Admin Bootstrap

entrytwo is designed so the very first user in a fresh installation becomes an ADMIN.

  • If no ADMIN exists, the sign-in / onboarding flow allows creating one (company language + standard chart of accounts).
  • After the first admin exists, further users join via invitation (Admin → Users). They must sign in with the exact invited email using Google, Microsoft, or magic link.
  • Roles: ADMIN, ACCOUNTANT, or VIEWER.
  • In production, ADMIN must enable TOTP (2FA) before using the app.

See First Steps for the first-login experience.


Authentication (summary)

EnvironmentHow you sign in
Local developmentInstant email (“Continue”) — no SMTP required
ProductionMagic link (SMTP) and/or Google OAuth and/or Microsoft Entra ID

Instant email is disabled when NODE_ENV=production. Configure OAuth redirect URIs to {AUTH_URL}/api/auth/callback/google and .../microsoft-entra-id.


Health and Basic Verification

  • App health: GET /api/health
  • Production should report "ready": true and auth.productionReady: true
  • After docker compose up -d, the app container has a healthcheck on this endpoint

Optional: local Git/npm smoke (fresh installs only)

Use this only on a brand-new local npm run dev install. It creates the first admin and company. Do not run it against a real business instance, a Docker production stack, or any database that already has users.

POST /api/auth/dev returns 403 in production (NODE_ENV=production), including published Docker images.

curl --fail-with-body --silent --show-error http://localhost:3000/api/health
node scripts/check-install.mjs http://localhost:3000

# Only instead of browser onboarding, with the owner's chosen details:
node scripts/check-install.mjs http://localhost:3000 --bootstrap --email you@example.com --company "My Company"

The bootstrap command verifies development providers and refuses an existing administrator before writing records. It runs onboarding before dev login, checks the returned ADMIN session, and holds cookies only in memory. It does not write a cookie file. The app initializes .data/files and .data/backups at startup too; the explicit mkdir step documents the required storage layout. Clone-only agents can read llms.txt, AGENTS.md, and skills/entrytwo/SKILL.md from the repository without fetching the website.


Production VPS (short)

Do these before expecting a green production boot:

  1. Secrets — AUTH_SECRET, ENCRYPTION_KEY, JOB_RUNNER_SECRET, POSTGRES_PASSWORD (fresh per install).
  2. Sign-in — full SMTP (SMTP_HOST + USER + PASS) and/or Google/Microsoft OAuth. Instant email is off in production.
  3. Public URL — AUTH_URL=https://your.domain, AUTH_TRUST_HOST=true.
  4. DNS — A record for the hostname → VPS IP before HTTPS. If certificates failed while DNS was missing, restart Caddy/nginx after DNS works.
  5. Deploy — Path B uses docker compose up -d; its entrypoint runs migrations. Path A runs Node.js natively: apply prisma migrate deploy, build with npm run build, run npm start under a process manager, and run node --env-file=.env scripts/job-worker.mjs as a separate managed worker. Supply the same environment to both processes. Confirm /api/health → ready: true.
  6. First admin — bootstrap, then TOTP for ADMIN.

RAM: plan ≥ 2 GB for one stack; ~4 GB if you run multiple installs (separate compose projects, ports, and hostnames) on one machine.

Installation files and update manifests: public releases. Application source: felixkrusch/entrytwo. Troubleshooting: common issues.


Common Next Steps

  • Read First Steps
  • Set up at least one LLM key and test the chat
  • Configure SMTP and/or OAuth so teammates can sign in
  • (Production) HTTPS, strong secrets, and the VPS auth checklist
  • (Later) Bank feeds, updates, and licensing

See also: