Skip to content

Deployment

Domekologe edited this page Jun 30, 2026 · 2 revisions

Deployment

🌐 English · Deutsch

For production you build the SvelteKit app with adapter-node and run it with node build, rather than npm run dev. The fastest path is the ready-made systemd installer; Docker and a manual build are also supported. This page covers the production build, the installer, the systemd unit, Docker, an nginx reverse proxy, and the five gotchas that bite people in real deployments. Configure your .env first (Configuration).

Screenshot: dashboard running behind a reverse proxy on a custom domain

Production build (manual)

npm ci
npm run build
node -r dotenv/config build      # starts adapter-node (default port 3000)

node build does NOT read .env. adapter-node only uses real environment variables. Load them with node -r dotenv/config build (needs npm i dotenv), set -a; . ./.env; set +a, or a systemd EnvironmentFile= (below). This is the single most common production mistake.

Ready-made systemd service (recommended)

From the project folder on the server:

cp .env.example .env && nano .env     # configure once
sudo bash deploy/install-service.sh   # builds + installs the systemd service + starts it

The script is idempotent: it creates the dks system user if needed, runs npm ci && npm run build, writes the systemd unit, then enables and starts it. It is configurable via environment variables:

APP_DIR=/opt/dks/redbot-dks-dashboard SERVICE_USER=dks SERVICE_NAME=dks-dashboard \
  sudo -E bash deploy/install-service.sh

Afterwards:

journalctl -u dks-dashboard -f          # live logs
sudo systemctl restart dks-dashboard    # restart
sudo bash deploy/update.sh              # update: fetch + build + restart

The systemd unit

The installer writes a unit equivalent to this. Note the EnvironmentFile= line — this is how .env reaches node build in production:

# /etc/systemd/system/dks-dashboard.service
[Unit]
Description=PDC Redbot Webapp (SvelteKit/adapter-node)
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/dks/redbot-dks-dashboard
EnvironmentFile=/opt/dks/redbot-dks-dashboard/.env
ExecStart=/usr/bin/node build
Restart=on-failure
RestartSec=5
User=dks
Group=dks
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now dks-dashboard

The systemd .env format is one KEY=value per line — no export, no quotes.

Docker

cp .env.example .env
docker compose up -d --build      # http://localhost:3000

If the bot runs on the host while the app runs in a container, point the gateway at the host:

GATEWAY_URL=http://host.docker.internal:6970

The compose file maps host.docker.internal via extra_hosts, so this also works on Linux.

nginx reverse proxy

A single catch-all location / proxying to the Node port is enough. Do not add a root/try_files that would intercept /_app/….

server {
    server_name your-domain;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
    }
}

When behind HTTPS, also set the reverse-proxy variables in .env (otherwise you get e.g. a 403 on logout):

ORIGIN=https://your-domain
PROTOCOL_HEADER=x-forwarded-proto
HOST_HEADER=x-forwarded-host

The five gotchas

  1. node build does not read .env. Use node -r dotenv/config build, set -a; . ./.env; set +a, or systemd EnvironmentFile=.
  2. Behind an HTTPS reverse proxy set ORIGIN, PROTOCOL_HEADER and HOST_HEADER, or actions like logout fail with 403.
  3. A Tailwind config must exist. Without tailwind.config.(cjs|js), Tailwind emits only the base layer and the page is (almost) unstyled. Never delete it; commit it to git. With "type":"module", tailwind.config.cjs (CommonJS) is most robust.
  4. The proxy must pass /_app/ through. A single catch-all location / to the Node port is enough; no root/try_files intercepting /_app/....
  5. CDN cache (e.g. Cloudflare): assets are cached immutable. While iterating, purge the cache after rebuilds or use "Development Mode".

Related pages

Clone this wiki locally