A small upload endpoint I run on my home server so ShareX can drop files into a plain folder and get a link back.
The reason it exists is that I was tired of S3 keeping all my uploads as opaque blobs when all I wanted was the real file sitting in a directory I can open and serve myself. ShareX could do this over SFTP, but I'd rather not expose that on a box at home, and a plain HTTP endpoint feels cleaner anyway. So this is the piece in between. It checks a token, writes the upload to disk under a random name, and returns the URL.
It picked up a few things along the way like token auth so I can hand out separate keys, quotas so one key can't eat the whole disk, a built-in file browser with instant search, and web dashboard logins for both admins and upload users to check their usage or browse their files.
| Admin Dashboard | File Browser & Search |
|---|---|
![]() |
![]() |
| Token Creation & Management | User Profile & Quotas |
![]() |
![]() |
There's a published image at ghcr.io/skidoodle/uploadserver. Grab
compose.yaml from the repo, set the environment how you want, and bring it up:
docker compose up -dUploads go to the data volume, tokens to the state volume.
Releases has prebuilt Linux/amd64 tarballs. Download the latest one, extract, run.
git clone https://github.com/skidoodle/uploadserver
cd uploadserver
go build -o uploadserver .The first run prints a root token once. Save it, that's your admin login:
$ uploadserver run
uploadserver: (default root token: Xq3...9fZ - saved to ./state/tokens.db)
uploadserver: listening on :8080, storing uploads in ./dataMake an upload token for ShareX from the dashboard: open the server's URL and log in with the root token. The CLI does the same, but only while the server is stopped — the token store is a bbolt database and the running server holds it open, so a CLI command run alongside it exits with a "locked" message. Manage tokens through the dashboard while the server is up, and reach for the CLI when it's down:
$ uploadserver add --label laptop --role upload
created upload token a1b2c3d4
secret (shown once): <your-upload-token>Then import uploadserver.sxcu into ShareX, set the URL to your domain, and
drop the token into the Authorization header. A plain curl does the same job:
$ curl -H "Authorization: Bearer <token>" -F "file=@cat.png" https://u.example.com/
https://u.example.com/a1b2c3d4.pngThe link is BASE_URL plus the random filename. uploadserver writes the files
but doesn't serve them, so point your web server at the upload folder (nginx,
caddy, whatever) and set BASE_URL to wherever that lands.
$ uploadserver
usage: uploadserver <command> [<args>]
Commands:
run Start the web server
list List all tokens, with usage and quotas
add [--label L] [--role R] Create a new token
rm <id> Delete a token
disable <id> Disable a token
enable <id> Enable a token
limit <id> [flags] Set upload quotas for a token
global [flags] Show or set the server-wide default quota
scan [--token ID] Find untracked files on disk and optionally import them
dump Decode the binary store and print everything in it
reset Delete all tokens and reset store
limit and global take the quota flags:
--total-size 5GB Lifetime size cap (B/KB/MB/GB/TB; 0 clears it)
--total-uploads 1000 Lifetime upload-count cap
--monthly-size 5GB Size cap per calendar month
--monthly-uploads 500 Upload-count cap per calendar month
limit also has --bypass to exempt a token from all quotas and --clear to
wipe its caps. Only the flags you pass get changed.
Because uploadserver holds an exclusive database lock while running, CLI commands (scan, add, limit, etc.) must be run in a temporary container while the main server container is stopped.
With Docker Compose:
# Stop the server container to release the database lock
docker compose stop uploadserver
# Run any CLI command (e.g. scan)
docker compose run --rm uploadserver scan
# Restart the server container
docker compose start uploadserverWith plain Docker:
# Stop the main container
docker stop uploadserver
# Run the CLI command in a temporary container sharing volumes
docker run --rm --volumes-from uploadserver ghcr.io/skidoodle/uploadserver:latest scan
# Restart the main container
docker start uploadserverIf you already have files in the upload directory from a previous setup or
manual copy, scan finds them and optionally imports them into a token's
history so quotas and the admin dashboard reflect the real state of the disk.
$ uploadserver scan
found 3 untracked file(s) in ./data:
FILE SIZE MODIFIED
a1b2c3d4e5f6.png 1.2 MB 2025-03-14 09:22
deadbeef1234.jpg 340 KB 2025-03-15 11:05
cafebabe5678.mp4 48.7 MB 2025-04-01 16:30
to import these files, re-run with --token <id>To adopt them into a token:
$ uploadserver scan --token a1b2c3d4
imported 3 file(s) into token a1b2c3d4Everything is set through environment variables:
| Variable | Description | Default |
|---|---|---|
LISTEN_ADDR |
Address to listen on | :8080 |
UPLOAD_DIR |
Directory uploads are written to | ./data |
BASE_URL |
Public URL prefix for returned links | the request's host |
UPLOAD_FIELD |
Multipart form field name | file |
TOKEN_STORE |
Path to the bbolt token database | ./state/tokens.db |
MAX_UPLOAD_BYTES |
Max upload size in bytes | 1073741824 (1 GiB) |
RANDOM_NAME_LENGTH |
Length of the random filename in hex chars | 32 |
STRIP_EXTENSION |
Drop the file extension from returned URLs | false |
ENABLE_ADMIN |
Mount the admin dashboard and API | true |
SERVE_FILES |
Serve uploaded files | false |



