Skip to content
Q
QuoteNode

Installation

Install on a Windows LAN Server

Run QuoteNode with Docker Desktop on a Windows PC inside an office network.

Install on a Windows LAN Server

Use this path when a Windows PC in the office should host QuoteNode for users on the same local network.

When to use it

  • The sales team works inside one office LAN.
  • Customers do not need to open offer links from the public internet.
  • You want an internal beta without managing a public VPS.

What will not work

  • Public offer links and notification links will only work for devices on the same LAN or VPN. External customers cannot open a private LAN address.
  • Email delivery can work with SMTP, but links inside emails still point to the LAN URL.
  • Windows sleep, reboots, or Docker Desktop updates can interrupt service.

Prerequisites

  • Docker Desktop with Compose v2 enabled.
  • A stable LAN IP address or DHCP reservation for the Windows host.
  • At least 2 GB RAM available and 10 GB free disk space.
  • Regular backup location outside the Docker volume.

Verify your setup in PowerShell or Command Prompt:

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 in the project directory. Replace 192.168.1.50 with the actual IP of the Windows host (or an internal DNS name like quotenode.local). 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, e.g. in Git Bash or WSL? 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 — UNIQUE to this installation.
# 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

# LAN URL — replace with your PC's IP or internal DNS name
CORS_ALLOWED_ORIGINS=http://192.168.1.50
DOMAIN=192.168.1.50

# 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. Store a copy outside this PC. If you lose DB_ENCRYPTION_KEY, encrypted fields become permanently unreadable.

Step 3 — Create the Docker Compose file

Create a file named docker-compose.yml:

services:
  postgres:
    image: postgres:18-alpine
    cpus: 4.0
    mem_limit: 512m
    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: 448m
    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: 4.0
    mem_limit: 1792m
    depends_on:
      postgres: { condition: service_healthy }
      gotenberg: { condition: service_healthy }
    env_file: .env
    environment:
      SPRING_PROFILES_ACTIVE: prod
      JOBS_MODE: web
      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: 2.0
    mem_limit: 192m
    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 on the first run. Watch the startup:

docker compose logs -f backend

Wait until you see Started QuoteNodeApplication.

Step 5 — Open QuoteNode

Navigate to http://192.168.1.50 (or your PC address) from any device on the LAN.

Default login credentials:

FieldValue
Email[email protected]
PasswordAdmin123!

Change the default password immediately after logging in.

Windows firewall

  • Allow inbound TCP on port 80 for HTTP access from the LAN.
  • Allow inbound TCP on port 443 only if you configure HTTPS.
  • Do not expose PostgreSQL to the LAN unless you have a specific admin need.
  • Keep Docker Desktop running under an account and startup policy appropriate for your office.

What is running?

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 service status
docker compose ps

# View logs
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

SecretWhat it protectsIf lost
DB_PASSWORDDatabase accessRecoverable — reset via PostgreSQL admin tools
DB_ENCRYPTION_KEYMFA codes, SMTP password, FX API key, PII fieldsPermanently unreadable
TIMING_TOKEN_SECRETAnti-timing-attack tokensRegenerate — users re-authenticate
PUBLIC_LINK_PASSWORD_SESSION_SECRETPublic link sessionsRegenerate — active sessions expire

The critical secret is DB_ENCRYPTION_KEY. Store .env backups outside this PC. See Installation troubleshooting for recovery steps.

Last reviewed: Recently