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
.envfile immediately. Store a copy outside this server. If you loseDB_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:
| Field | Value |
|---|---|
[email protected] | |
| Password | Admin123! |
Change the default password immediately after logging in.
What is running?
| Container | What it does | RAM usage |
|---|---|---|
postgres | Stores all your data | ~256 MB |
backend | Java API server, business logic | ~512 MB – 1.5 GB |
gotenberg | Converts HTML to PDF (Chromium-based) | ~200 MB |
frontend | Serves 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
| Secret | What it protects | If lost |
|---|---|---|
DB_PASSWORD | Database access | Recoverable — reset via PostgreSQL admin tools |
DB_ENCRYPTION_KEY | MFA codes, SMTP password, FX API key, PII fields | Permanently unreadable |
TIMING_TOKEN_SECRET | Anti-timing-attack tokens | Regenerate — users re-authenticate |
PUBLIC_LINK_PASSWORD_SESSION_SECRET | Public link sessions | Regenerate — active sessions expire |
The critical secret is
DB_ENCRYPTION_KEY. Store.envbackups outside this server. See Installation troubleshooting for recovery steps.
Recommended next 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.