Skip to content
Q
QuoteNode

Installation

Install on a Linux LAN Server

Run QuoteNode on an internal Linux server for an office or VPN-only team.

Install on a Linux LAN Server

Use this path when QuoteNode should run on an internal Linux server, VM, or mini-PC reachable by the office network or VPN.

When to use it

  • Several internal users need a stable shared instance.
  • Public customer access is not required.
  • You prefer a Linux host over Docker Desktop.

What will not work

  • Public offer links work only for users who can reach the server address.
  • Email delivery can work with SMTP, but recipients outside the LAN cannot open internal links.
  • Self-signed internal HTTPS may show browser warnings unless your organization trusts the certificate authority.

Prerequisites

  • Ubuntu, Debian, or another maintained Linux distribution.
  • Docker Engine 24.0+ and Docker Compose v2.
  • A static IP or DHCP reservation for the server.
  • SSH access for operators.
  • At least 2 GB RAM and 10 GB free disk space.
  • Backup storage outside the main Docker volume.

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. Replace 192.168.1.20 with your server’s actual LAN IP or internal DNS name (e.g. crm.internal). 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 — 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 server IP or internal DNS name
CORS_ALLOWED_ORIGINS=http://192.168.1.20
DOMAIN=192.168.1.20

# 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

If you deploy behind an internal reverse proxy with HTTPS, use the HTTPS origin instead:

CORS_ALLOWED_ORIGINS=https://crm.internal.example
DOMAIN=crm.internal.example

Save your .env file immediately. Store a copy outside this server. 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.20 (or your server address) from any device on the LAN.

Default login credentials:

FieldValue
Email[email protected]
PasswordAdmin123!

Change the default password immediately after logging in.

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 server. See Installation troubleshooting for recovery steps.

  • Configure scheduled backups and test a restore.
  • Test PDF generation after first deployment.
  • Use Ubuntu public server or Coolify when public offer links must work for customers.

Last reviewed: Recently