Snapp is one of those projects worth revisiting.

I covered it before in a broader list of open-source URL shorteners, but the Docker setup in that older post is now stale. The old snippet used Redis-era Snapp variables like DB_HOST and AUTH_SECRET. Current Snapp has moved to a Postgres-backed SvelteKit app with Better-Auth, Drizzle, organizations, teams, multi-domain configuration, analytics, and API access.

So this post is the updated deployment note.

What is Snapp?

Snapp is a self-hosted URL shortening platform.

I inspected the current upstream branch:

beta-version-1
b847b4d65314f24993d8fadbaf91bfb19dcc84f1

The current app is not just a tiny redirect table. It includes:

  • short URLs with optional custom shortcode, notes, tags, expiration, hit limits, and UTM templates;
  • multi-domain workspaces, where each configured host maps to an organization;
  • Better-Auth users, organizations, roles, API keys, and team permissions;
  • redirect metrics for browser, OS, device, language, referrer, geo, and UTM data;
  • optional Umami server-side tracking;
  • optional VirusTotal API validation;
  • a Scalar API reference UI;
  • runtime theme overrides through config/custom.css.

Why the Old Snapp Setup Is Outdated

The older round-up post showed a Redis-based Compose snippet using:

uraniadev/snapp:0.7.test
DB_HOST
AUTH_SECRET
redis/redis-stack

That is not the current deployment model.

The current upstream README expects:

Postgres
DATABASE_URL
BETTER_AUTH_SECRET
config/settings.yaml

That settings.yaml detail matters. Snapp resolves the request origin against configured hosts. If you open the app from a URL that is not listed in config/settings.yaml, Snapp rejects the request.

During validation, the app returned:

The application is not configured to run from this origin.

when the browser-facing origin did not match the configured host.

Snapp vs Dub

Snapp and Dub both live in the URL shortener/link-management space, but they have very different operating profiles.

Dub is much heavier. As covered in the Dub post, it is a cloud-native link attribution platform with MySQL/PlanetScale, Upstash Redis/QStash, Tinybird/ClickHouse, S3/R2 object storage, Vercel-oriented deployment assumptions, custom domains, and analytics infrastructure.

Snapp is more approachable for a home lab:

  • one web app container;
  • one Postgres container;
  • a mounted config folder;
  • a normal reverse proxy in front when deployed publicly.

It is still more capable than a toy shortener, but it does not ask you to assemble the Dub-style managed SaaS data plane.

Self-Hosting with Docker

The reusable Home-Lab compose file is here:

The site includes the same Compose file from the local snippets folder:

assets/snippets/snapp/docker-compose.yml
includeyaml: file not found or not allowed by security.filesystem.allow: assets/snippets/snapp/docker-compose.yml

Prepare the environment:

cd assets/snippets/snapp
cp .env.sample .env

Generate secrets:

openssl rand -base64 32
openssl rand -base64 32

Use one generated value for:

POSTGRES_PASSWORD

Use the other generated value for:

BETTER_AUTH_SECRET

Start the stack:

docker compose up -d

The default local URL in this snippet is:

http://localhost:3800

The Important Config File

Snapp reads its host configuration from:

config/settings.yaml

The provided local snippet includes:

appname: Snapp

admin:
  - email: [email protected]
    username: admin

hosts:
  - origin: "http://localhost:3800"
    options:
      customRedirect: "/dashboard"
      disable:
        homepage: false
        twoFactor: true

smtp:
  enabled: false

If you deploy Snapp at a real domain, change the origin:

hosts:
  - origin: "https://links.example.com"

Snapp creates an organization for each configured host. The origin is not just cosmetic; it is part of how the app scopes access and decides which workspace the request belongs to.

Reverse Proxy Notes

For a public deployment, put Snapp behind HTTPS.

A typical setup is:

https://links.example.com -> reverse proxy -> http://snapp:3000

Then set:

hosts:
  - origin: "https://links.example.com"

If you keep the app on a local port, make sure the origin in settings.yaml matches the exact URL you open in the browser.

Field Test

I tested the current container path in a bounded way.

The old Home-Lab file did not start as-is on this host because it published port 8000, which was already allocated. It also omitted Postgres and the current required Snapp environment variables.

I then started a disposable current stack with:

uraniadev/snapp:latest
postgres:16-alpine
DATABASE_URL
BETTER_AUTH_SECRET

Results:

  • Postgres became healthy.
  • Snapp ran its database migrations.
  • Snapp generated the initial admin account.
  • http://127.0.0.1:3800/ returned 401 when the origin did not match settings.yaml.
  • The same app returned HTTP 200 when the request host matched the configured origin.

That last point is the practical gotcha: configure settings.yaml before assuming the app is broken.

FAQ

Does Snapp still use Redis?

Not in the current setup I inspected. The current README and code use Postgres, Drizzle, and Better-Auth.

What database does Snapp use now?

Postgres.

What is BETTER_AUTH_SECRET?

It is the application auth secret used by Better-Auth. Generate a long random value and keep it stable after first deployment.

Why do I get an origin error?

Your browser URL does not match hosts[].origin in config/settings.yaml. Update the config to the exact public URL you use.

Can I use a custom domain?

Yes. Configure your reverse proxy and set the matching domain in config/settings.yaml.

Is Snapp lighter than Dub?

Yes. Snapp is a much simpler self-hosted stack: app plus Postgres. Dub is a larger cloud-native attribution platform with multiple specialized services.

Verdict

Snapp is a good fit when you want a modern self-hosted URL shortener without the infrastructure weight of Dub.

The key is using the current Postgres-based deployment model and treating config/settings.yaml as required configuration, not an optional extra.