AssetR

Installation & deployment

AssetR runs as a Docker Compose stack — the app plus Postgres. This is the intended, supported deployment path; it's written to be production-ready as-is (no hardcoded secrets, no dev-only services). A reverse proxy, HTTPS/TLS, and a domain are deliberately out of scope here — put something in front of it, or access it directly over http://host:3000.

Would rather not manage Docker, Postgres, or a reverse proxy yourself? See managed hosting — we run a private instance for you instead.

Prerequisites

  • Docker Desktop, or another Docker Compose v2-compatible engine, installed and running.
  • Two files — docker-compose.yml and a .env:
mkdir assetr && cd assetr
wget https://assetr.io/downloads/docker-compose.yml
wget https://assetr.io/downloads/env.example -O .env

The second command saves env.example straight as your working .env — nothing to separately copy or rename.

On a native Linux host (a VPS, not Docker Desktop on Windows/Mac), also create the bind-mounted data directories and hand them to the container's user before first start:

mkdir -p docker/pgdata docker/uploads docker/backups
sudo chown -R 1001:1001 docker/uploads docker/backups

The app container runs as a non-root user (uid 1001) for security, and Compose creates missing bind-mount directories as root — without this, the app can start fine but fail with EACCES: permission denied the moment it actually writes to one of these (e.g. clicking “Create backup now”, or uploading a company logo). Postgres's own image fixes up docker/pgdata's ownership itself on first start, so that one doesn't need this.

Then edit .env and set:

  • POSTGRES_PASSWORD — a real password, used both to configure the Postgres container and to build the app's database connection string. Required — Compose refuses to start without it.
  • AUTH_SECRET — a random password, used internally to keep login sessions secure (it's what encrypts the "you're logged in" cookie, so nobody can forge one). Generate one with npx --yes auth secret, or without Node.js: openssl rand -base64 32. Either way, paste the printed value in as-is.
  • AUTH_URL — must be the exact scheme + host + port a browser uses to reach this server, or login will fail (every redirect/callback bounces back to whatever is set here). Pick whichever matches your setup:
    • No domain, hitting the server's IP directly: http://YOUR_SERVER_IP:3000
    • A domain pointed at the server, no TLS in front: http://yourdomain.com:3000
    • A domain with your own reverse proxy terminating TLS in front (nginx/Caddy/etc. — not included in this deployment): https://yourdomain.com
  • APP_IMAGE / APP_VERSION — which published image and tag to pull:

Setting up SSO later? It reuses this same AUTH_URL — no separate env var. When registering the app with your identity provider, its callback/redirect URL is ${AUTH_URL}/api/auth/callback/oidc. The issuer URL, client ID, and client secret themselves are entered in-app, on Settings, not in .env. A dedicated, in-app step-by-step setup guide (Settings → Single Sign-On → “Setup guide”) walks through the exact steps for Entra ID, Okta, Auth0, Google Workspace, and Keycloak once you're deployed.

APP_IMAGE="msd29/assetr.io"
APP_VERSION="1.0.0"
  • SMTP_* — optional. Leave blank to run with no email notifications configured (handled gracefully), or fill in real provider details now or later from the in-app Settings page.
  • TZ — optional. The IANA timezone (e.g. Asia/Kolkata) the container runs in — determines what wall-clock hour the daily backup and email notification checks actually fire at. Defaults to UTC if left unset.

Quick start: pull the published image

AssetR is distributed as a prebuilt Docker image — no source code, build step, or Dockerfile is needed on the host at all. Once .env is filled in:

docker compose pull
docker compose up -d

docker compose pull fetches exactly ${APP_IMAGE}:${APP_VERSION} as set in .env — the image is public, so no docker login is required to pull it.

Watch the logs to confirm it's healthy:

docker compose logs -f app

You're looking for:

✓ Ready in ...ms
[notifications] scheduled check registered (daily at 08:00)
[backups] scheduled check registered (daily at HH:MM)

Both of those times are in whatever TZ is set to — UTC if you haven't set it. If backups seem to run at an unexpected hour, this is almost always why: set TZ to your own IANA timezone rather than entering a local time on the in-app Backups page and expecting it to be interpreted as such.

Then open http://localhost:3000 (or whatever AUTH_URL points at) — it redirects to a first-run setup page on a fresh database, or to login otherwise.

Upgrading to a new version

  1. Take a backup first from the in-app Backups page — your rollback safety net.
  2. Check Docker Hub (or this site's release notes) for the tag you want, and set APP_VERSION in .env to it.
  3. Pull and restart: docker compose pull && docker compose up -d
  4. Database migrations for the new version run automatically on that same restart — there's no separate migration step to remember.

Database migrations

Handled automatically on every container start, before the server begins serving traffic — nothing to run by hand for the normal upgrade path.

Configuring email

There's no bundled dev mail-capture service — run with either no SMTP configured (notifications simply won't send, and “send test email” shows a clear error) or real provider credentials, set as SMTP_* in .env before first start, or anytime afterward live from Settings.

Backups & data persistence

Postgres data, uploaded photos, and database backups are all bind-mounted to the host and persist across down/up cycles and image rebuilds. To start completely fresh (recommended before a first real deployment):

docker compose down
rm -rf ./docker/pgdata ./docker/uploads ./docker/backups

This permanently deletes the database, uploaded photos, and any backups made — only do this if that data doesn't need to be kept.

Stopping

docker compose down

Stops and removes the containers without touching any of your persisted data.