dibs is a small self-hosted garage sale catalog. Public visitors browse one shared catalog of published items and use each item's contact link. The owner manages the site and can invite sellers through single-use invite links; each seller manages only their own items.
- Install dependencies:
pnpm install-
Copy
.env.exampleto.envand fill in the values. -
Start PostgreSQL, then run migrations:
pnpm db:migrate- Start the app:
pnpm devThe public catalog is at http://localhost:5173; the admin area is at /admin.
Required:
DATABASE_URLADMIN_USERNAMEADMIN_PASSWORD_HASHSESSION_SECRETUPLOAD_DIRBODY_SIZE_LIMITfor production multipart request size, default30Min Docker ComposePUBLIC_SITE_URLORIGINwhen running the production Node server behind Docker or a proxy
ORIGIN must match the browser origin used to access the app, for example http://localhost:3000 for local Docker Compose or https://dibs.example.com in production. SvelteKit uses this for CSRF protection on admin form posts.
Photo uploads are validated by the app at 25 MB per file. Uploaded jpg, png, and webp images are resized to fit within 1600x1600 and stored as webp files for the public site. BODY_SIZE_LIMIT must be larger than the largest expected multipart upload request; Docker Compose defaults it to 30M.
Generate a bcrypt hash:
pnpm password:hash "your-admin-password"Use the printed value as ADMIN_PASSWORD_HASH.
The first login claims the pending owner account using ADMIN_USERNAME and ADMIN_PASSWORD_HASH. After that the database row is the source of truth, and changing those environment variables has no effect on the established owner account.
As the owner you can:
- Invites (
/admin/invites): create single-use signup links for a WhatsApp number that expire in 1–30 days, and revoke unused ones. The normalized number becomes the seller's immutable account identity. - Sellers (
/admin/tenants): see seller accounts and their item counts, disable or re-enable accounts (disabling signs them out immediately), and issue one-time temporary passwords.
Sellers choose a unique username during signup. From Profile (/admin/profile), every account can edit its username, display name, and contact method. Contact methods are either a normalized WhatsApp number or email address; account identity cannot be edited.
Sellers only ever see and manage their own items. All published items appear together in the public catalog; item detail pages show the seller's display name when set, and the contact button uses the seller's typed contact method. An account must configure a contact method before publishing an item.
pnpm db:generate # generate Drizzle migrations from schema changes
pnpm db:migrate # apply migrationsSet ADMIN_PASSWORD_HASH and SESSION_SECRET, then run:
docker compose up --buildCompose starts PostgreSQL and the app, applies migrations, serves the app on http://localhost:3000, and stores uploads in the uploads_data volume. If you browse through http://127.0.0.1:3000, start Compose with ORIGIN=http://127.0.0.1:3000 as well.
The included Dockerfile builds a standalone app image. The default container command is:
node scripts/migrate.mjs && node buildThat means the same image can run directly in Coolify without Docker Compose, as long as Coolify provides PostgreSQL separately.
Coolify setup:
- Create a PostgreSQL resource in Coolify.
- For GitHub Actions deploys, deploy a Docker Image app using
ghcr.io/stulentsev/dibs:latest. - Expose internal port
3000. - Set
DATABASE_URLto the PostgreSQL resource connection string. - Set
ORIGINandPUBLIC_SITE_URLto the public HTTPS URL for the app. - Set
UPLOAD_DIR=/app/uploads. - Set
BODY_SIZE_LIMIT=30Mor larger if you want to allow bigger image upload requests. - Add a persistent volume mounted at
/app/uploads. - Uploaded photos are written under
UPLOAD_DIR, so they persist across rebuilds as long as the volume remains mounted.
The publish workflow (.github/workflows/publish-image.yml) pushes the image to GHCR and then triggers a Coolify redeploy when Coolify secrets are configured. If the Coolify secrets are absent, the GHCR publish still succeeds and the deploy step is skipped.
Repository secrets for the Coolify deploy handoff:
COOLIFY_WEBHOOK: Deploy webhook URL from the Coolify app's Webhook page.COOLIFY_TOKEN: Coolify API token with deploy permission.
The container image referenced by your Coolify app should stay aligned with this repository image (ghcr.io/stulentsev/dibs:latest).
If you choose to let Coolify build the repository directly instead, deploy this repository as a Dockerfile-based app and use the same environment and volume settings.
Required app environment variables for Coolify:
DATABASE_URL=postgres://...
ADMIN_USERNAME=owner
ADMIN_PASSWORD_HASH=...
SESSION_SECRET=...
UPLOAD_DIR=/app/uploads
BODY_SIZE_LIMIT=30M
PUBLIC_SITE_URL=https://your-dibs-domain.example
ORIGIN=https://your-dibs-domain.examplepnpm check
pnpm build
pnpm test:e2epnpm test:e2e expects PostgreSQL to be reachable at postgres://dibs:dibs@localhost:5432/dibs_e2e by default. Override it with E2E_DATABASE_URL if your local test database is elsewhere. The E2E setup resets that database schema before running.