Installation
Install with Portainer, QNAP, or NAS
Deploy QuoteNode on a NAS or Portainer-managed Docker host with full setup instructions.
Install with Portainer, QNAP, or NAS
Use this path if you run Docker through Portainer, QNAP Container Station, Synology, or a similar platform. The exact UI differs by vendor, but the underlying Docker Compose stack is the same.
When to use it
- You already operate Docker stacks through a container management UI.
- You understand how the platform maps volumes and ports.
- You can verify backups outside the NAS UI.
What may not work
- Public links do not work outside the LAN unless NAT, DNS, proxy, and HTTPS are configured.
- NAS sleep modes can interrupt service.
- Some NAS Docker UIs rewrite compose files or environment variables — verify the rendered stack before relying on it.
- Backups stored only on the same NAS do not protect against NAS failure.
Prerequisites
- Docker Compose v2 support in your platform.
- Persistent volume support for PostgreSQL data, uploads, PDFs, and backups.
- At least 2 GB RAM available and 10 GB free disk space.
- A stable URL: LAN IP for internal use, or a public domain behind a reverse proxy for customer links.
Step 1 — Create the configuration file
Create a .env file (or set variables through your platform’s environment UI). Replace the URL with your actual NAS address or public domain. 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.)
For an internal NAS-only deployment:
# 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
# Internal NAS URL
CORS_ALLOWED_ORIGINS=http://nas.local
DOMAIN=nas.local
# 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
For a public domain behind a reverse proxy:
CORS_ALLOWED_ORIGINS=https://crm.example.com
DOMAIN=crm.example.com
Save your
.envimmediately. If you loseDB_ENCRYPTION_KEY, encrypted fields become permanently unreadable.
Step 2 — Create the Docker Compose stack
Use the stack editor in Portainer or your NAS Docker UI, or create a docker-compose.yml file:
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:
Port conflicts: If your NAS already uses port 80 or 443, change the left side of the
portsmapping (e.g."8080:80") and updateCORS_ALLOWED_ORIGINSaccordingly (e.g.http://nas.local:8080).
Step 3 — Deploy and verify
Start the stack through your platform UI or via SSH:
docker compose up -d
docker compose logs -f backend
Wait until you see Started QuoteNodeApplication.
Step 4 — Open QuoteNode
Navigate to your NAS URL (e.g. http://nas.local or https://crm.example.com).
Default login credentials:
| Field | Value |
|---|---|
[email protected] | |
| Password | Admin123! |
Change the default password immediately after logging in.
Persistence checklist
Verify these are mapped to persistent volumes (not ephemeral container storage):
| Data | Why it matters |
|---|---|
| PostgreSQL data | Main application database |
| Uploads | Product images, logos, attachments |
| Generated PDFs | Offer and document files |
| Backups | Local backup archives (if backup worker is enabled) |
| GeoIP database | Optional access-control data |
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.
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 |
Store
.envbackups outside the NAS. A NAS failure that destroys your only copy ofDB_ENCRYPTION_KEYmakes encrypted data unrecoverable.
Recommended next steps
- Configure backups to a location outside the NAS.
- Test PDF generation after first deployment.
- If you want a smoother public deployment path, consider Coolify on a VPS.