A patched, production-ready Docker image of fiche/termbin, the minimal TCP pastebin (cat file | nc domain 9999).
This fork fixes four silent bugs present in the upstream C source and adds self-service slug deletion support.
cat file | nc your.domain 9999
# → https://your.domain/a3f9b2c1d4e5f6a7The recommended setup runs two containers:
- fiche — accepts TCP uploads, writes slugs to disk, handles deletions
- termbin-web — nginx serves pastes as plain text over HTTP (put your reverse proxy in front)
git clone https://github.com/Leproide/termbin-reborn# Public domain returned in URLs
FICHE_DOMAIN=your.domain
# TCP port for uploads
FICHE_PORT=9999
# fiche flags (see below for reference)
FICHE_ARGS=-S -s 16 -B 10485760 -l /data/log/fiche.log -P 9998
# TCP port for delete requests
FICHE_DELETE_PORT=9998
# nginx internal port (loopback only — put a reverse proxy in front)
NGINX_PORT=62365index.html in the repo root is served at https://your.domain/ by nginx. Edit it to add a description, usage instructions, or branding for your instance. It is mounted as a read-only bind mount and takes effect without rebuilding the image:
volumes:
- "./index.html:/srv/static/index.html:ro"docker compose up -dservices:
# fiche: TCP pastebin
fiche:
image: leprechaunit/fiche:latest
container_name: fiche
restart: unless-stopped
ports:
- "${FICHE_PORT:-9999}:9999" # upload
- "${FICHE_DELETE_PORT:-9998}:9998" # delete: echo -e "slug\ntoken" | nc domain 9998
environment:
FICHE_DOMAIN: ${FICHE_DOMAIN}
FICHE_ARGS: ${FICHE_ARGS}
volumes:
- "./data/paste:/data/paste"
- "./data/log:/data/log"
# termbin-web: nginx static file server
termbin-web:
image: nginx:alpine
container_name: termbin-web
restart: unless-stopped
ports:
- "127.0.0.1:${NGINX_PORT:-62365}:80"
volumes:
- "./data/paste:/srv/paste:ro"
- "./nginx.conf:/etc/nginx/nginx.conf:ro"
- "./index.html:/srv/static/index.html:ro"The two containers share ./data/paste — fiche writes, nginx reads. The nginx port is bound to 127.0.0.1 only; a reverse proxy (Caddy, nginx, Apache…) on the host should front it and terminate TLS.
| Variable | Default | Description |
|---|---|---|
FICHE_DOMAIN |
(required) | Public hostname returned in uploaded URLs |
FICHE_PORT |
9999 |
Host port mapped to the upload listener |
FICHE_DELETE_PORT |
9998 |
Host port mapped to the delete listener |
FICHE_ARGS |
(none) | Extra flags passed directly to fiche (see below) |
NGINX_PORT |
62365 |
Internal nginx port, bound to 127.0.0.1 |
| Flag | Description |
|---|---|
-S |
Return https:// links instead of http:// |
-s <n> |
Slug length (default: 4) |
-B <bytes> |
Max upload size in bytes (default: 32768; 10 MB = 10485760) |
-l <path> |
Log file path inside the container |
-L <addr> |
Listen address (default: 0.0.0.0) |
-p <port> |
Listen port (default: 9999) |
-u <user> |
Drop privileges to this user after bind |
-P <port> |
Delete service port (0 = disabled) |
When -P <port> is set, every upload returns both the paste URL and a ready-to-run delete command:
$ cat file.txt | nc your.domain 9999
https://your.domain/a3f9b2c1d4e5f6a7
To delete this paste:
echo -e "a3f9b2c1d4e5f6a7\n<token>" | nc your.domain 9998
Copy and run that line to permanently remove the paste. The token is generated once at upload time and stored server-side, there is no way to retrieve it afterwards, so save the delete command if you need it later.
| Container path | Purpose |
|---|---|
/data/paste |
Slug directories (shared with nginx) |
/data/log |
fiche log file |
Mount both with bind mounts so data survives container restarts:
volumes:
- "./data/paste:/data/paste"
- "./data/log:/data/log"| Port | Protocol | Purpose |
|---|---|---|
9999 |
TCP | Upload |
9998 |
TCP | Delete |
The receive buffer was a VLA on the thread stack. Any -B value larger than the default 32 KB caused an immediate silent stack overflow: the thread died, no URL was returned, no file was saved.
Fix: heap allocation via malloc, grown dynamically per connection.
MSG_WAITALL combined with SO_RCVTIMEO caused recv to return ≤ 0 when the client sent fewer bytes than buffer_len and closed the connection — the normal case for cat file | nc. All data was discarded silently.
Fix: replaced with a read loop that accumulates data until EOF or the buffer limit is reached.
The original approach called calloc(buffer_len) for every connection regardless of upload size. A 5-byte paste with -B 10485760 would allocate and hold 10 MB per thread.
Fix: buffer starts at 64 KB and grows via realloc only as needed up to -B. malloc_trim(0) is called after each connection to return freed heap memory to the OS immediately.
free(c) was called before close(c->socket), reading a freed struct field — undefined behaviour flagged by -Wuse-after-free.
Fix: reordered cleanup so the socket is closed before the struct is freed.
- Patched source and full Docker stack: https://github.com/Leproide/termbin-reborn
- Upstream original: https://github.com/solusipse/fiche
- Docker HUB: https://hub.docker.com/r/leprechaunit/fiche