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
Pre-Requisites - Docker
Install Docker on your system before proceeding:
- Linux: Official Docker Engine install guide
- Windows / Mac: Docker Desktop
Verify installation: docker --version && docker compose version
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/returned401when the origin did not matchsettings.yaml.- The same app returned HTTP
200when 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.
Comments