Discard is a self-hosted chat application for private friend groups on Tailscale. A single Go server provides the API, WebSocket text chat, WebRTC voice and screen sharing, and the built Svelte frontend; PostgreSQL stores application data.
| Component | Technology |
|---|---|
| Backend | Go 1.27, pgx, Gorilla WebSocket |
| Frontend | SvelteKit 2, Svelte 5, TypeScript, Vite 8 |
| Database | PostgreSQL 17 |
| Voice and screen sharing | Pion WebRTC with embedded TURN fallback |
| Authentication | Tailscale identity, with a development-only bypass |
| Deployment | Single binary, Docker, or Docker Compose |
- Go 1.27
- Node.js 24.19 or newer and npm 11
- PostgreSQL 17
ffmpeg,yt-dlp, andcwebpfor media processing- Tailscale for normal authentication and private-network access
Create a PostgreSQL database and start the backend:
export DATABASE_URL='postgres://localhost:5432/discard?sslmode=disable'
export DISCARD_DEV=true
go run ./cmd/discardIn another terminal, install the frontend dependencies and start Vite:
cd web
npm ci
npm run devOpen https://localhost:5173. Development mode exposes fixed test identities through ?dev_user=0 to ?dev_user=3; never enable DISCARD_DEV in production.
go test -race ./...
go vet ./...
cd web
npm ci
npm run check
npm run buildThe Playwright suite requires PostgreSQL, the backend on port 4000, and the Vite HTTPS server on port 5173:
cd web
npx playwright install chromium
npm run test:e2eThe browser suite verifies bidirectional audio over both direct ICE and forced TURN relay connections.
Set the required secrets in an ignored .env file:
TS_AUTHKEY=tskey-auth-...
TURN_SECRET=replace-with-a-random-secretThen start the Tailscale sidecar, application, and PostgreSQL:
docker compose up -d --buildCompose stores PostgreSQL data, uploads, and Tailscale state in named volumes. It does not publish the application on the host network; access is through the Tailscale node and tailscale serve.
| Variable | Default | Purpose |
|---|---|---|
PORT |
4000 |
Backend HTTP port |
DATABASE_URL |
postgres://localhost:5432/discard?sslmode=disable |
PostgreSQL connection string |
UPLOAD_DIR |
./uploads |
Uploaded-file directory |
DISCARD_DEV |
false |
Disable Tailscale authentication and expose fixed development users |
DISCARD_PRODUCTION |
false |
Prevent accidental use of development authentication in production |
TAILSCALE_API_URL |
Local Tailscale API | Override the identity API endpoint |
TAILSCALE_API_TOKEN |
Unset | Authenticate to the macOS Tailscale local API |
TURN_SECRET |
Random per startup | Shared secret used for ephemeral TURN credentials |
TURN_PUBLIC_IP |
Auto-detected tailnet IP | Override the client-reachable TURN relay address |
TURN_PORT |
3478 |
Embedded UDP TURN listener port |
API_TARGET |
http://localhost:4000 |
Vite development proxy target |
Set a persistent TURN_SECRET in deployed environments. Set TURN_PUBLIC_IP when automatic tailnet-address discovery is not suitable.
- Blocking prevents direct-message creation, sending, and editing in both directions.
- Existing direct-message history remains available, and either participant may delete their own messages.
- Presence is hidden in both directions.
- Shared-server messages remain visible. Only the blocker sees the blocked author's name and avatar anonymized.
- Unblocking restores direct-message sending without deleting or recreating the conversation.
make buildThe frontend output is copied to internal/frontend/build for embedding in the production Go binary. The Dockerfile performs this process in reproducible build stages.