Deploy Wastebin
Tiny Rust pastebin — encrypted, burn-after-reading, expirations, QR
wastebin-lite
Just deployed
/data
Wastebin Lite
A 20 MB single-binary pastebin. Fast, small, self-hostable on one container. Encrypted pastes, burn-after-reading, expirations, markdown rendering, QR codes, 9 themes, 170+ syntax-highlighted languages — built in Rust/Axum with no Node runtime.
Deploy and Host
One click. Railway provisions a single service with the /data volume, sets the two per-install secrets, and wires the public URL. Nothing to configure unless you want to change the theme or paste size limit.
- Runtime — Rust (statically linked, scratch base). No Node.js, no Python, no JVM.
- Memory — ~20 MB at rest. Fits on Railway Hobby.
- Port — 8088 (baked into the Docker wrapper).
- Persistence — SQLite on the
/datavolume. Pastes survive redeploys and restarts. - Public URL —
WASTEBIN_BASE_URLis auto-filled fromRAILWAY_PUBLIC_DOMAINon every new deploy.
Why Deploy
- Sticky by design. Once you start keeping logs, API responses, snippets, and secrets in one place, a second pastebin never gets installed.
- Encrypted by choice. Password-protected pastes use ChaCha20-Poly1305 + argon2 — for keys, credentials, incident notes.
- Burn-after-reading built in. A paste can self-destruct after a one-time confirmation reveal, so you can pass a secret URL to someone without leaving a trail.
- Tiny binary. One ~20 MB static ELF. Deploys in seconds; restarts are instant.
- Keyboard-first UI.
rraw,nindex,ycopy URL,ccopy content,qQR,wwrap,mmarkdown toggle,?all keybindings. - API-first. Every paste feature is reachable with pure
curl— no browser required. Scripts and CI can share long outputs without a UI.
Common Use Cases
- Log capture & share — pipe
journalctl,kubectl logs,tail -f, or CI output into a paste and paste the URL next to the incident. - Encrypted secret handoff — paste a password or API key, share the URL privately, let the recipient use their own password header to unlock.
- Burn-after-reading — share a token to a single person who must click through a confirm page exactly once; the paste is gone afterwards.
- Code / config snippet sharing — syntax-highlighted paste, rendered markdown view, raw endpoint for pipelines.
- QR for mobile —
/qr/{id}prints a phone-scannable QR of the paste URL (convenient for logs you want to read on a phone). - Expiring pastes — 10 min, 1 h, 1 d, 1 M, 1 y options out of the box.
- Agent & bot log drop — paste long tracebacks and logs for another service (or a human) to fetch.
Deployment Dependencies
No external database, cache, or queue:
- Railway Hobby or above — 1 GB RAM is plenty; the binary uses ~20 MB at rest.
/datavolume — SQLitestate.dblives here; the template attaches it automatically. Without it, pastes vanish on redeploy.- No Postgres, no Redis, no external auth — everything is embedded.
Architecture
One service, one container, one volume:
| Piece | Value |
|---|---|
| Container | quxfoo/wastebin:3.7.1 (scratch base) + root wrapper in this repo |
| Port | 8088 (baked into the Dockerfile; Railway reverse-proxy + external healthcheck target /) |
| Volume | /data — mounted by Railway; SQLite state.db persists here |
| Secrets | per-install WASTEBIN_SIGNING_KEY (>= 64 bytes) + WASTEBIN_PASSWORD_SALT (16 bytes) |
| Domain | auto-generated by Railway; exposed at 8088 |
The wrapper runs as root because Railway mounts persistent volumes as root-owned; quxfoo/wastebin ships as a non-root user and would EACCES-crash-loop writing the database. The scratch base has no shell, so the healthcheck is Railway's external probe of / (there is no in-container HEALTHCHECK to write). The Dockerfile pins WASTEBIN_DATABASE_PATH=/data/state.db; without it, Wastebin writes the DB to the container's writable layer and every redeploy silently wipes all pastes.
Features
- Encrypted pastes — ChaCha20-Poly1305 + argon2 password protection.
- Burn-after-reading — confirm once, reveal once, then 404.
- Expirations —
0,10m,1h,1d,1M,1y(configurable per deploy). - 1 MiB max body — raise
WASTEBIN_MAX_BODY_SIZEfor long logs; 170+ languages highlighted. - Markdown render —
/md/{id}with GitHub tables, task lists, admonitions. - Raw view —
/raw/{id}for pipes,curl, CI scripts. - QR code —
/qr/{id}for phone sharing. - Theme picker — 9 themes: ayu, base16ocean, catppuccin, coldark, gruvbox, monokai, onehalf, rosepine, solarized.
- Owner tokens — every paste URL carries a signed delete token so the author can revoke it.
- Keyboard-first UI — full keybinding layer in the browser.
Dependencies for Wastebin Lite
- Railway Hobby or above — 1 GB RAM is plenty; the binary uses ~20 MB at rest.
/datavolume — required for persistence. The template attaches it automatically.- No external database, cache, or queue — SQLite is embedded in the container.
About Hosting
Wastebin Lite is a single container. Railway's reverse proxy terminates TLS at the edge and forwards plain HTTP to the container on port 8088; the external healthcheck targets /. The root wrapper exists solely to make the Railway volume mount writable by the Wastebin process — the binary itself is unchanged from the upstream quxfoo/wastebin:3.7.1 image.
Configuration
| Variable | Default | Description |
|---|---|---|
WASTEBIN_BASE_URL | ${{RAILWAY_PUBLIC_DOMAIN}} | Public URL for the paste links in the UI and the API response. |
WASTEBIN_TITLE | Wastebin Lite | Site title in the browser tab. |
WASTEBIN_THEME | catppuccin | Default theme (see Features). |
WASTEBIN_MAX_BODY_SIZE | 1048576 | Max paste size in bytes (1 MiB). Raise for long logs. |
WASTEBIN_PASTE_EXPIRATIONS | 0,10m,1h,1d,1M,1y | UI expiry options (0 = no expiry). |
WASTEBIN_SIGNING_KEY | ${{secret(64)}} | Required, >= 64 bytes — the server refuses to start on a shorter key. Signs owner/delete tokens. Auto-generated per install. |
WASTEBIN_PASSWORD_SALT | ${{secret(16)}} | Argon2 salt used to derive the encryption password key. Auto-generated per install. |
How to Use
1. Deploy
The template creates one service with the /data volume. On first deploy it sets the two per-install secrets and wires WASTEBIN_BASE_URL to your public domain — no manual configuration needed.
2. Paste in the browser
Open YOUR-DOMAIN, type or paste content, choose an expiry and (optionally) an extension and password, press Enter. Keyboard: r raw, n index, y copy URL, c copy content, q QR, w wrap, m markdown toggle, ? all keybindings.
3. Paste with the API
# Plain paste (1 MiB default, .log extension, 1-day expiry)
curl -s -X POST -H "Content-Type: application/json" \
-d '{"text":"CPU 90% I/O 3% Uptime 42d","extension":"log","expires":86400,"burn_after_reading":false}' \
https://YOUR-DOMAIN/
# -> {"path":"/abcd-ef.log","owner":"SIGNED-OWNER-TOKEN"}
# Raw content
curl https://YOUR-DOMAIN/raw/abcd-ef.log
# Encrypted paste
curl -s -X POST -H "Content-Type: application/json" \
-d '{"text":"secret-key-material","password":"mypass"}' \
https://YOUR-DOMAIN/
# Read back with the password header:
curl -H "wastebin-password: mypass" https://YOUR-DOMAIN/raw/abcd-ef.log
# Burn after reading
curl -s -X POST -H "Content-Type: application/json" \
-d '{"text":"self-destructing","burn_after_reading":true}' \
https://YOUR-DOMAIN/
# The normal URL redirects to /burn/abcd-ef — confirm once, then the paste 404s.
Pipe anything in:
journalctl -u systemd --since "1 hour ago" | curl -s --data-binary @- \
-H "Content-Type: text/plain" https://YOUR-DOMAIN/
4. Endpoints
| Method | Path | Description |
|---|---|---|
POST | / | Create a paste. JSON or raw body. Returns {path, owner}. |
GET | /{id} | HTML view. Burn-protected pastes redirect to /burn/{id}. |
GET | /raw/{id} | Unprocessed paste body. |
GET | /md/{id} | Rendered markdown view. |
GET | /qr/{id} | QR code of the paste URL. |
GET | /burn/{id} | One-time confirm interstitial for burn pastes. POST confirm_burn=1 reveals and burns. |
GET | /{id}?owner=SIGNED-OWNER-TOKEN | Owner handshake — signs the uid session cookie authorizing delete. |
DELETE | /{id} | Delete the paste (requires the uid cookie from the handshake). |
POST | /delete/{id} | Browser-form delete (same uid cookie requirement). |
GET | /theme | Switch theme (query `pref=DARK |
Troubleshooting
- Container crash-loops on a fresh install — confirm
WASTEBIN_SIGNING_KEYis >= 64 bytes (the server refuses to start on a shorter key). The${{secret(64)}}value satisfies this. - Pastes vanish after a redeploy —
WASTEBIN_DATABASE_PATHmust point at the volume (/data/state.db, baked into the Dockerfile) and the/datavolume must be attached to the service. - Encrypted pastes unreadable after a redeploy —
WASTEBIN_PASSWORD_SALTchanged between deploys; regenerate the paste or pin a stable salt value. - Owner token 403 / paste not deleted — the token contains base64
+and/; when passing it as a query parameter, URL-encode it, and always perform theGET /{id}?owner=SIGNED-OWNER-TOKENhandshake before theDELETEorPOST /delete/{id}. - Paste too large — bump
WASTEBIN_MAX_BODY_SIZE(bytes) and redeploy. - Port / health — the container listens on
0.0.0.0:8088(baked into the Dockerfile); the external healthcheck targets/.
License
Upstream Wastebin is MIT-licensed. This template's wrapper Dockerfile and config are provided as-is.
Template Content
wastebin-lite
mc9max/wastebin-lite