Skip to content
Q
QuoteNode

Setup

Installation Guide

How to deploy QuoteNode on your own infrastructure using Docker Compose.

Installation

QuoteNode runs as a set of Docker containers. You do not need to install Java, Node.js, or PostgreSQL on your machine — Docker handles everything.

Prerequisites

  • Docker Engine 24.0 or later (install Docker)
  • Docker Compose v2 (included with modern Docker Desktop and Docker Engine)
  • A machine with at least 2 GB RAM and 10 GB free disk space

To verify your setup:

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

Quick Start (5 minutes)

This gets QuoteNode running on your local machine for evaluation. Not for production — see below for that.

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:

# 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 (or even a printout).
# ============================================================
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

# Logging
LOG_LEVEL=INFO
SPRING_PROFILES_ACTIVE=prod

Save your .env file immediately. The secrets above were randomly generated for you and are unique to this installation. If you lose this file, you cannot regenerate the same values. Without the original DB_ENCRYPTION_KEY, encrypted data (MFA codes, SMTP password, API keys) becomes permanently unreadable. Copy this file to a safe location — for production systems, consider printing the secrets and storing them in a physical safe.

Step 3 — Create the Docker Compose file

Create a file named docker-compose.yml:

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    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
    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
    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
    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) and start all services. You can watch the startup progress with:

docker compose logs -f backend

Wait until you see a line like 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. Understanding what each secret protects helps you plan your backup strategy:

SecretWhat it protectsIf lost
DB_PASSWORDDatabase accessRecoverable — reset via PostgreSQL admin tools
DB_ENCRYPTION_KEYMFA (TOTP) codes, SMTP password, FX API key — and all PII fields when ENCRYPT_PII=truePermanently unreadable — these fields 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. If ENCRYPT_PII=true, this also includes all customer, contact, supplier, and user PII (names, emails, phones, tax IDs). Without PII encryption enabled, your business data is stored unencrypted and remains fully accessible.

For production systems, we recommend:

  • Store a copy of .env in a password manager (1Password, Bitwarden, etc.)
  • For maximum safety, print the secrets and keep the printout in a physical safe
  • Never commit .env to version control

Recovery if .env is lost

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

  1. Create a fresh .env with new secrets
  2. Restore the database from your backup (pg_dump / pg_restore)
  3. All business data (customers, offers, products, users) will be fully accessible
  4. Only a few fields are lost: MFA codes (users will need to re-enroll), SMTP password (re-enter in settings), FX API key (re-enter in settings). If ENCRYPT_PII=true was enabled, all PII data (customer names, emails, phones, tax IDs) is also lost — consider this carefully before choosing PII encryption

If your backups are GPG-encrypted (via BACKUP_GPG_RECIPIENT), you need the GPG private key to decrypt them first.

Production deployment

For production use, you need to:

  1. Generate proper secrets — never use the example values above in production:
# Generate a 64-character hex key for database encryption
openssl rand -hex 32

# Generate random secrets for tokens
openssl rand -base64 32
  1. Set up a domain with HTTPS — configure your DOMAIN and CORS_ALLOWED_ORIGINS to your real domain. The frontend container (Caddy) automatically obtains Let’s Encrypt certificates.

  2. Configure email — add SMTP settings to your .env to enable offer sending:

SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USERNAME=[email protected]
SMTP_PASSWORD=your-smtp-password
SMTP_AUTH=true
SMTP_STARTTLS=true
  1. Back up your .env file — store it securely before anything else. See Keeping your secrets safe above.

  2. Enable database backups — see the Backup & Recovery guide.

  3. Review all settings — download the full .env.prod.example from the repository for a complete reference of all available configuration variables.

For advanced deployment options (Coolify PaaS, backup workers, GeoIP), see the Deployment Options wiki page.

Updating QuoteNode

To update to the latest version:

docker compose pull && docker compose up -d

Your .env file is never modified by updates. Docker pulls new container images but your configuration and data remain untouched. Do not delete or recreate your .env file during updates — the same secrets must be used throughout the lifetime of your installation.

Database schema migrations run automatically on backend startup — no manual migration step is needed.

Troubleshooting

Backend won’t start

Check the logs: docker compose logs backend. Common issues:

  • Database not ready — the backend waits for PostgreSQL to be healthy. If PostgreSQL is slow to start, the backend retries automatically.
  • Invalid secretsDB_ENCRYPTION_KEY must be exactly 64 hex characters. DB_PASSWORD must match between the backend and PostgreSQL containers.
  • Port conflict — if port 80 or 443 is already in use, change the ports mapping in docker-compose.yml.

Cannot connect to QuoteNode

  • Verify all containers are running: docker compose ps
  • Check that the frontend container shows “healthy”
  • Try accessing http://localhost (not https:// for local testing without a real domain)

Forgot admin password

Stop the stack, delete the PostgreSQL volume, and restart — the admin account is recreated on first boot:

docker compose down -v   # WARNING: deletes all data!
docker compose up -d

For production, use the password reset feature in the admin panel instead.

Next steps

Last reviewed: Recently