Skip to content
Q
QuoteNode

Installation

Install on a Local Laptop

Run QuoteNode locally for evaluation without exposing it to clients.

Install on a Local Laptop

Use this path when you want to try QuoteNode on one computer. It is the fastest way to evaluate the application, but it is not a client-facing deployment.

When to use it

  • You want a demo or trial instance.
  • You are testing offer creation, PDFs, templates, products, and settings.
  • You do not need customers to open public offer links from another device.

What will not work

  • Public offer links generated as http://localhost/... work only on the same laptop.
  • Email links are usually not useful because recipients cannot open localhost from their machines.
  • HTTPS is normally not configured for this topology.
  • The laptop must be awake and Docker must be running.

Prerequisites

  • Docker Desktop or Docker Engine with Docker Compose v2.
  • At least 2.5 GB RAM available for the stack.
  • At least 10 GB free disk space.

Verify your setup:

docker --version        # Should show 24.0+
docker compose version  # Should show v2.x

Step 1 — Create a project directory

mkdir quotenode && cd quotenode

Step 2 — Create the configuration file

Create a file named .env with the minimum required settings. The secret values below are filled with fresh random values in your browser on each page load — use the ↻ New secrets button on the block to regenerate them, then copy the result. (Prefer to generate them yourself? See Deployment environment variables.)

# Database
DB_URL=jdbc:postgresql://postgres:5432/quotenode
DB_USERNAME=quotenode
DB_PASSWORD=change-me-to-something-random-32-chars
DB_NAME=quotenode

# ============================================================
# SECURITY SECRETS — READ THIS BEFORE CONTINUING
# These values are UNIQUE to your installation.
# If you lose this file, you CANNOT regenerate the same secrets.
# SAVE THIS FILE and keep a secure backup.
# ============================================================
DB_ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
TIMING_TOKEN_SECRET=change-me-timing-secret-min-32-chars
PUBLIC_LINK_PASSWORD_SESSION_SECRET=change-me-session-secret-min-32

# Domain (for CORS and Caddy)
CORS_ALLOWED_ORIGINS=http://localhost
DOMAIN=localhost

# Client IP is auto-detected behind internal proxies and CDNs (Cloudflare included) — leave empty.
# Set only for an unusual proxy whose transport peer is a PUBLIC address to trust (IP/CIDR or hostname).
SECURITY_TRUSTED_PROXIES=

# Application
LOG_LEVEL=INFO
SPRING_PROFILES_ACTIVE=prod

Save your .env file immediately. The DB_ENCRYPTION_KEY encrypts sensitive data at rest. If you lose it, encrypted fields (MFA codes, SMTP password, API keys) become permanently unreadable. Copy this file to a safe location.

Step 3 — Create the Docker Compose file

Create a file named docker-compose.yml:

services:
  postgres:
    image: postgres:18-alpine
    cpus: 2.0
    mem_limit: 384m
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      PGDATA: /var/lib/postgresql/data   # required for the postgres:18 image
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME}"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped

  gotenberg:
    image: gotenberg/gotenberg:8
    cpus: 2.0
    mem_limit: 384m
    environment:
      LOG_LEVEL: info
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  backend:
    image: ghcr.io/lesisty7/quotenode/quotenode-api:latest
    cpus: 2.0
    mem_limit: 1280m
    depends_on:
      postgres: { condition: service_healthy }
      gotenberg: { condition: service_healthy }
    env_file: .env
    environment:
      SPRING_PROFILES_ACTIVE: prod
      # Single-container eval (WEB_MAINTENANCE backup profile): the backend runs
      # scheduled backups in-process, so no separate backup-worker container is needed.
      # For production, split into a web backend + a dedicated backup-only worker.
      JOBS_MODE: all
      BACKUP_RUNTIME_PROFILE: WEB_MAINTENANCE
      PDF_ENABLED: "true"
      PDF_GOTENBERG_URL: http://gotenberg:3000
    volumes:
      - backend_uploads:/app/data/uploads
      - backend_pdfs:/app/data/pdfs
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
      interval: 15s
      timeout: 10s
      retries: 5
      start_period: 45s
    restart: unless-stopped

  frontend:
    image: ghcr.io/lesisty7/quotenode/quotenode-frontend:latest
    cpus: 1.0
    mem_limit: 128m
    depends_on:
      backend: { condition: service_healthy }
    ports:
      - "80:80"
      - "443:443"
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:80/"]
      interval: 15s
      timeout: 5s
      retries: 3
    restart: unless-stopped

volumes:
  postgres_data:
  backend_uploads:
  backend_pdfs:

Step 4 — Start QuoteNode

docker compose up -d

Docker will download the container images (this takes a few minutes on the first run). Watch the startup progress:

docker compose logs -f backend

Wait until you see Started QuoteNodeApplication — this means the backend is ready.

Step 5 — Open QuoteNode

Navigate to http://localhost in your browser.

Default login credentials:

FieldValue
Email[email protected]
PasswordAdmin123!

Change the default password immediately after logging in (Settings > My Profile > Change Password).

What is running?

After docker compose up, you have 4 containers:

ContainerWhat it doesRAM usage
postgresStores all your data~256 MB
backendJava API server, business logic~512 MB – 1.5 GB
gotenbergConverts HTML to PDF (Chromium-based)~200 MB
frontendServes the web UI + reverse proxy~20 MB

Total: approximately 1.5 – 2.5 GB RAM.

Common commands

# Check if all services are running
docker compose ps

# View logs (all services)
docker compose logs -f

# View logs (backend only)
docker compose logs -f backend

# Stop QuoteNode
docker compose down

# Update to the latest version
docker compose pull && docker compose up -d

Keeping your secrets safe

Your .env file contains cryptographic secrets that are unique to your installation:

SecretWhat it protectsIf lost
DB_PASSWORDDatabase accessRecoverable — reset via PostgreSQL admin tools
DB_ENCRYPTION_KEYMFA codes, SMTP password, FX API key, and all PII fields when ENCRYPT_PII=truePermanently unreadable — cannot be decrypted without the original key
TIMING_TOKEN_SECRETAnti-timing-attack tokensRegenerate — existing tokens invalidated, users re-authenticate
PUBLIC_LINK_PASSWORD_SESSION_SECRETPublic link sessionsRegenerate — active sessions expire, links still work

The critical secret is DB_ENCRYPTION_KEY. All other secrets can be regenerated with minor inconvenience. But DB_ENCRYPTION_KEY encrypts data at rest — losing it means those fields are gone forever.

Recovery if .env is lost

If you lose your .env file but have an unencrypted database backup (the default backup mode):

  1. Create a fresh .env with new secrets.
  2. Restore the database from your backup.
  3. All business data (customers, offers, products, users) will be fully accessible.
  4. Only a few fields are lost: MFA codes (users re-enroll), SMTP password (re-enter in settings), FX API key (re-enter in settings). If ENCRYPT_PII=true was enabled, encrypted customer data is also lost.

Backups

This single-container setup runs scheduled backups inside the backend process (JOBS_MODE: all), so you do not need a separate backup-worker container. The backup itself runs as a shell pipeline (pg_dump → compress → encrypt) outside the JVM, so its memory cost is small — this is why the laptop backend is sized at 1280m rather than 1g.

This is acceptable for a laptop or evaluation instance. For a production deployment, run a dedicated backup-only worker container (JOBS_MODE: backup-only) alongside a web backend (JOBS_MODE: web) so backups never compete with web traffic for memory. See Backup & Recovery.

Moving from laptop to server

Do not publish laptop links to customers. When you are ready for a shared or public deployment, choose one of these paths:

Last reviewed: Recently