Manual Setup

Choose-your-own-adventure. Each step has options — pick what fits your stack. Prefer a click-through? Use the wizard.

00

Prerequisites

  • Linux host (any distro). 1 vCPU, 1 GB RAM, 10 GB disk minimum.
  • A DNS A/AAAA record pointing at your host (or a Cloudflare Tunnel).
  • Docker 24+ and Docker Compose plugin v2.
  • Git.
# Ubuntu/Debian one-liner
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER  # log out / in after
01

Clone the source

git clone https://github.com/joshhearne/resolvd.git
cd resolvd
02

Generate secrets

You need two: a Postgres password and a session secret. Both 32+ chars.

openssl rand -hex 32  # SESSION_SECRET
openssl rand -base64 32  # POSTGRES_PASSWORD

Also set FRONTEND_URL to your public base URL and COOKIE_SECURE=true when serving over HTTPS (always, in prod). The shipped .env.example is missing COOKIE_SECURE — add it explicitly:

FRONTEND_URL=https://yourdomain.com
COOKIE_SECURE=true
03

Pick your auth providers

You can mix and match. Local always works as a fallback.

Option A — Local accounts only Easiest

Nothing to configure. After first boot, the login page shows a Create Admin Account form — the first account created there becomes Admin. Subsequent users are invited from Admin → Users.

Option B — Microsoft Entra ID Workspace SSO
  1. Go to Entra admin center → App registrations → New.
  2. Redirect URI (Web): https://yourdomain.com/auth/callback
  3. API permissions: User.Read, optionally Mail.Send for email.
  4. Certificates & secrets → New client secret. Copy it now.
  5. Drop these into your .env:
AZURE_TENANT_ID=...
AZURE_CLIENT_ID=...
AZURE_CLIENT_SECRET=...
AZURE_REDIRECT_URI=https://yourdomain.com/auth/callback
AZURE_ALLOWED_ORIGINS=https://yourdomain.com
Option C — Google OAuth Workspace or consumer
  1. Open Google Cloud Console → APIs & Services → Credentials.
  2. Create OAuth client ID → Web application.
  3. Authorized redirect URI: https://yourdomain.com/auth/google/callback
  4. Copy client ID and secret into .env:
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=https://yourdomain.com/auth/google/callback
04

Pick your email backend

Optional. Skip entirely if you don't want notifications.

Pick the backend after first boot in Admin → Authentication → Email. The SMTP host/credentials live in the database, not .env.

Microsoft GraphReuses Entra app

Add Mail.Send application permission to your Entra app, then grant admin consent. The only env var needed is the sender address:

MAIL_FROM=noreply@yourdomain.com
Gmail APIReuses Google app

Enable Gmail API in your Google Cloud project. Add gmail.send scope. Sender + service-account JSON are configured in Admin → Authentication → Email.

SMTPPostmark, SendGrid, etc.

Drop host, port, user, password into Admin → Authentication → Email. No env vars required.

05

Bring up the stack

cp .env.example .env  # paste in everything from steps 2–4
mkdir -p data/uploads
docker compose up -d --build
docker compose logs -f

Stack now bound to 127.0.0.1:8090. Not reachable externally yet.

06

Pick your reverse proxy

Pick one. All terminate TLS and forward to 127.0.0.1:8090.

CaddyAuto-TLS, easiest
# /etc/caddy/Caddyfile
yourdomain.com {
    reverse_proxy 127.0.0.1:8090
    request_body { max_size 50MB }
}
sudo systemctl reload caddy
nginx + CertbotMost familiar
# /etc/nginx/sites-available/resolvd
server {
    listen 80;
    server_name yourdomain.com;

    client_max_body_size 50M;
    location / {
        proxy_pass http://127.0.0.1:8090;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
sudo ln -s /etc/nginx/sites-available/resolvd /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.com
Cloudflare Origin Cert + nginxIf you proxy via Cloudflare

If your domain is already on Cloudflare with proxy enabled, generate an Origin Certificate in the Cloudflare dashboard (SSL/TLS → Origin Server) and use the ready-to-use config shipped with the repo at nginx/host-proxy.conf:

sudo cp nginx/host-proxy.conf /etc/nginx/sites-available/resolvd
# Drop your origin cert + key into /etc/nginx/cloudflare/
sudo ln -s /etc/nginx/sites-available/resolvd /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Set Cloudflare SSL/TLS mode to Full (strict).

Cloudflare TunnelNo open ports
cloudflared tunnel login
cloudflared tunnel create resolvd
cloudflared tunnel route dns resolvd yourdomain.com

Edit ~/.cloudflared/config.yml:

tunnel: <tunnel-uuid>
credentials-file: ~/.cloudflared/<uuid>.json
ingress:
  - hostname: yourdomain.com
    service: http://127.0.0.1:8090
  - service: http_status:404
cloudflared service install
TraefikContainer-native

Add labels to the nginx service in docker-compose.yml and put it on your Traefik network:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.resolvd.rule=Host(`yourdomain.com`)"
  - "traefik.http.routers.resolvd.entrypoints=websecure"
  - "traefik.http.routers.resolvd.tls.certresolver=letsencrypt"
  - "traefik.http.services.resolvd.loadbalancer.server.port=80"
07

First-run setup

  1. Visit https://yourdomain.com.
  2. Login page shows Create Admin Account form — first account becomes Admin.
  3. Admin → Authentication: enable Entra/Google if you configured them.
  4. Admin → Branding: upload logo, set color, change site name.
  5. Admin → Users: invite your team, set roles.
  6. Create your first project. Done.
08

Backups

Two things to back up: the Postgres volume and the uploads volume.

# Postgres dump (run on the host)
docker exec resolvd-postgres pg_dump -U resolvd resolvd \
  | gzip > backup-$(date +%F).sql.gz

# Uploads (rsync or tar the volume)
docker run --rm -v resolvd_uploads-data:/src -v $(pwd):/dst \
  alpine tar czf /dst/uploads-$(date +%F).tar.gz -C /src .

Volume prefix is the Compose project name (default: install directory name). Legacy installs may use issues_* instead of resolvd_* — check with docker volume ls. Pin it across renames by setting COMPOSE_PROJECT_NAME.

Cron both nightly. Ship to S3/B2/wherever.

Stuck? Open an issue on GitHub.