Self-hosted temporary file sharing with auto-expiry by time and/or download count.
169
Self-hosted temporary file sharing. Upload a file, get a link, the link dies on a timer or after N downloads. No accounts, no tracking, no S3.
Built with FastAPI + SQLite. ~160 MB image, runs as non-root, single container.
Docker Hub: theinfobots/tmpshare
curl (multipart)/healthz endpoint for Docker healthchecksX-Forwarded-*)docker run -d --name tmpshare \
-p 8000:8000 \
-v tmpshare_data:/data \
--restart unless-stopped \
theinfobots/tmpshare:latest
Open http://localhost:8000.
services:
tmpshare:
image: theinfobots/tmpshare:latest
container_name: tmpshare
restart: unless-stopped
ports:
- "8000:8000"
environment:
PUBLIC_BASE_URL: https://files.example.com
MAX_FILE_SIZE: 1073741824 # 1 GiB
DEFAULT_EXPIRY_SECONDS: 86400 # 24 h
MAX_EXPIRY_SECONDS: 604800 # 7 d
CLEANUP_INTERVAL_SECONDS: 300
volumes:
- tmpshare_data:/data
volumes:
tmpshare_data:
All settings via environment variables:
| Variable | Default | Description |
|---|---|---|
DATA_DIR | /data | Directory for files.db + blobs/ |
MAX_FILE_SIZE | 1073741824 | Hard limit per upload, in bytes (1 GiB) |
DEFAULT_EXPIRY_SECONDS | 86400 | Default TTL when client omits one |
MAX_EXPIRY_SECONDS | 604800 | Upper bound on requested TTL |
CLEANUP_INTERVAL_SECONDS | 300 | Background purge interval |
PUBLIC_BASE_URL | (empty) | If set, returned links use this base (e.g. https://files.example.com) |
Persist /data to keep uploads across container restarts.
Go to /, pick a file, choose expiry hours and max downloads (0 = unlimited until expiry), submit. You get a download link and a delete link.
curl -F "file=@./report.pdf" \
-F "expires_in=3600" \
-F "max_downloads=1" \
https://files.example.com/api/upload
Response:
{
"id": "ohyZeK0h0g08Y94W",
"filename": "report.pdf",
"size": 482910,
"expires_at": 1778680778,
"max_downloads": 1,
"download_url": "https://files.example.com/d/ohyZeK0h0g08Y94W",
"delete_url": "https://files.example.com/api/delete/ohyZeK0h0g08Y94W?token=..."
}
curl -OJ https://files.example.com/d/ohyZeK0h0g08Y94W
Once max_downloads is reached the blob is purged within ~1 second; after expires_at the next request returns 410 Gone and triggers purge.
curl https://files.example.com/api/info/<id>
Use the delete_url returned at upload, or:
curl -X DELETE "https://files.example.com/api/delete/<id>?token=<delete_token>"
| Method | Path | Purpose |
|---|---|---|
GET | / | Web UI |
POST | /api/upload | Multipart upload (file, expires_in, max_downloads) |
GET | /d/{id} | Download (counts toward max_downloads) |
GET | /api/info/{id} | Metadata (no body) |
DELETE | /api/delete/{id}?token=... | Purge with delete token |
GET | /healthz | Liveness check |
# Tail logs
docker logs -f tmpshare
# Restart
docker restart tmpshare
# Update to latest image
docker pull theinfobots/tmpshare:latest
docker rm -f tmpshare
docker run -d --name tmpshare -p 8000:8000 \
-v tmpshare_data:/data --restart unless-stopped \
theinfobots/tmpshare:latest
# Backup data (DB + blobs)
docker run --rm -v tmpshare_data:/data -v "$PWD":/backup alpine \
tar czf /backup/tmpshare-$(date +%F).tgz -C / data
# Wipe everything
docker rm -f tmpshare && docker volume rm tmpshare_data
Nginx example:
server {
listen 443 ssl http2;
server_name files.example.com;
client_max_body_size 1100m; # match MAX_FILE_SIZE
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
}
}
Set PUBLIC_BASE_URL=https://files.example.com so generated download links use the right domain.
secrets.token_urlsafe(12)) — unguessable but not authenticated. Treat the URL as the credential. Anyone with the link can download until expiry.MAX_FILE_SIZE are rejected mid-stream with 413.latest — current buildYYYY-MM-DD — dated snapshotMIT.
Content type
Image
Digest
sha256:8e3391ab1…
Size
52.9 MB
Last updated
4 months ago
docker pull theinfobots/tmpshare