Self-hosted tool for moving clipboard text and files between a small trusted group's devices (phone, tablet, laptop) through shared, ephemeral streams called channels.
Installable PWA, real-time WebSocket sync, web push, resumable uploads, and automatic expiry — content stays on your machine.
How you run it: Docker Desktop
(or Docker Engine + Compose). There is no standalone .dmg / .exe server
installer; the stack is PostgreSQL + the app + a reverse proxy.
Two common setups:
| Home Wi‑Fi | Public URL | |
|---|---|---|
| Sign-in | Password (QP_AUTH_MODE=local) |
|
| Domain | Not needed | Yes (e.g. Cloudflare Tunnel) |
| Users | Single owner | Multi-user (allowlist / invites) |
MIT licensed. Spec: quick-paste-SPEC.md.
| Dark mode | Mobile |
|---|---|
![]() |
![]() |
Browser extension — send selection / link / image / clipboard from any tab:
| Toolbar popup | Settings |
|---|---|
![]() |
![]() |
- Docker Desktop (Mac / Windows / Linux) or Docker Engine with the Compose plugin
- ~2 GB free disk for images + data
- For Google mode: a public HTTPS URL and a Google OAuth Web client
- Install and start Docker Desktop.
- Get the code (
git cloneor Code → Download ZIP). - In a terminal, from the project folder:
./setup.shOn Windows, use WSL2 or Git Bash so the script can run.
setup.sh checks Docker, generates secrets, asks password vs Google, writes
.env, starts the stack, and prints the URL to open. You do not need to edit
config by hand.
Faster first start (skip the local build): after .env exists, set:
QP_SERVER_IMAGE=ghcr.io/vaibhavk81-git/quick-paste-server:latestthen docker compose up -d (or re-run ./setup.sh and keep the existing
.env). CI publishes that image from main. If docker pull fails with
unauthorized, the GHCR package may still be private — build with
docker compose up -d --build instead.
docker compose up -d # start / restart
docker compose down # stop (keeps data volumes)
docker compose logs -f server # follow server logs
docker compose pull && docker compose up -d # update when using QP_SERVER_IMAGEData lives in Docker volumes (qp-pgdata, qp-blobs, …). Back up those (and
.env) if you care about retention — especially once you use lasting data.
./setup.sh → choose Password. Or manually:
cp .env.example .env
# Set QP_AUTH_MODE=local and QP_LOCAL_PASSWORD=<strong password>
docker compose up -d --buildUse HTTPS on other devices. Caddy serves https://<host>:8443 with a local
CA. PWA install, web push, and the Android share target need a secure origin.
-
On each device, download and trust the CA once:
http://<host>:8080/qp-ca.crt- iOS: install the profile, then enable it under Settings → General → About → Certificate Trust Settings.
- Android: Settings → Security → install a CA certificate.
- macOS: open in Keychain Access → Always Trust.
-
Open
https://<host>:8443and sign in with your password.
Prefer a hostname (e.g. https://mybox.local:8443) over a raw IP when you can.
After the CA is trusted, set QP_LAN_HTTPS_ONLY=true in .env to redirect plain
HTTP to HTTPS (localhost and the tunnel stay exempt; /qp-ca.crt stays on HTTP).
Local mode is one user (owner@local). Anyone on the Wi‑Fi with the
password can use the stream — pick a strong password.
-
Create a Google Cloud OAuth client (Web application) with redirect URI
<QP_PUBLIC_URL>/api/auth/callback. -
Run
./setup.shand choose Google, or copy.env.example→.envand set at least:QP_PUBLIC_URL(https)QP_GOOGLE_CLIENT_ID/QP_GOOGLE_CLIENT_SECRETQP_ALLOWED_EMAILS(first email becomes admin)QP_DB_PASS,QP_SESSION_SECRETQP_TRUSTED_PROXY_COUNT=2if Cloudflare sits in front of Caddy
-
Point your public hostname at this machine. Easiest with the optional tunnel profile (set
CLOUDFLARE_TUNNEL_TOKENin.env):
COMPOSE_PROFILES=tunnel docker compose up -d --build- Open
QP_PUBLIC_URL, sign in with an allowlisted Google account.
Auth lives in the app. Cloudflare is ingress only — no Cloudflare Access required.
cp .env.example .env # fill required values — see .env.example and SPEC §11
docker compose up -d --build
curl -fsS http://localhost:8080/healthOpen http://localhost:8080/ (or your QP_PUBLIC_URL).
| Piece | Role |
|---|---|
Server (server/) |
Micronaut 4 / Java 17 — REST, WebSocket, OIDC or local auth, API tokens, TTL sweep |
| PostgreSQL | Persistence + pgvector (Library semantic search later) |
PWA (pwa/) |
Installable SPA (bundled into the server image) |
| Caddy | Only published ports (8080 / 8443); proxies to the server |
Extension (extension/) |
Chrome/Edge MV3 — token auth; see extension/README.md |
| Tunnel (optional) | cloudflared compose profile → your public hostname |
| Mac helper | Phase 3 Tauri menu-bar app (not built yet) |
server/ pwa/ extension/ shortcuts/ mac-helper/ docker-compose.yml
Outbound external sink channels (provider extension point) are specified in
SPEC §5.14; see docs/external-providers.md.
No branded third-party sink ships in the public tree.
Works today: Google + local-password auth, sessions, API tokens, channels,
text/file clips, simple + resumable uploads, thumbnails, web push, WebSocket
catch-up, TTL sweep, PWA, browser extension, iOS Shortcuts under
shortcuts/.
Not built yet: Mac menu-bar helper, per-channel E2E encryption, v3 Library.
For hacking on the server/PWA without the full compose stack:
# DB (host port 5433 avoids clashing with a local Postgres)
docker run -d --name qp-dev-db -p 5433:5432 \
-e POSTGRES_DB=quickpaste -e POSTGRES_USER=quickpaste \
-e POSTGRES_PASSWORD=quickpaste pgvector/pgvector:pg17
cd server && QP_DEV_AUTH_BYPASS=true \
QP_DB_URL=jdbc:postgresql://localhost:5433/quickpaste ./gradlew run
cd pwa && npm install && npm run dev # http://localhost:5173Backend tests (Testcontainers — Docker required): cd server && ./gradlew test.
More detail: CONTRIBUTING.md.




