Multi-protocol file server for network engineers. Firmware distribution, config push/pull, device provisioning — all in one Docker stack.
Browser ──── HTTP :80 ────► nginx ──► /public/ (static, no auth)
└─► FileBrowser (web UI, auth)
FTP client ── FTP :21 ───► vsftpd ── anonymous ► /public/ (read)
► /public/rw/ (read/write)
── auth user ► /data/ (full access)
Switch/AP ── TFTP :69 ───► tftpd ──► /public/ (read)
► /public/rw/ (read/write)
Network devices speak TFTP and FTP. Engineers want a web UI. Keeping three separate servers in sync is painful. netdrop runs all three protocols against a single shared directory, inside Docker.
git clone https://github.com/ChistokhinSV/netdrop.git
cd netdrop
# Set credentials (default: admin / changeme)
cp .env.example .env
vi .env
# Start
docker compose up -d --buildOpen http://<host>/ for the web UI, or point your devices at the server IP.
| Protocol | /public/ |
/public/rw/ |
/data/ |
|---|---|---|---|
| HTTP (static, no auth) | read | — | — |
| HTTP (FileBrowser, auth) | — | — | full |
| FTP anonymous | read | read/write | — |
| FTP authenticated | read/write | read/write | full |
| TFTP | read | read/write | — |
/public/— read-only zone. Place firmware images here via the web UI or authenticated FTP. Devices download via HTTP, FTP, or TFTP without credentials./public/rw/— writable zone. Devices can push configs and logs here via anonymous FTP or TFTP. No credentials required./data/— full storage root, accessible only through authenticated FTP or the web UI.
| Service | Image | Role |
|---|---|---|
| nginx | nginx:1.27-alpine |
Reverse proxy. Serves /public/ as static files, proxies everything else to FileBrowser |
| FileBrowser | ghcr.io/gtsteffaniak/filebrowser:stable |
Web UI for upload/download/manage. Single-user password auth |
| vsftpd | Custom (Ubuntu 24.04) | FTP server. Anonymous read + /rw/ write, virtual user with full access |
| tftpd-hpa | Custom (Ubuntu 24.04) | TFTP server. Read from /public/, write to /public/rw/ |
USERNAME='admin'
PASSWORD='changeme'Single credential pair shared across FileBrowser and FTP. Passwords are single-quoted to safely handle special characters ($, !, \, backticks).
| Port | Protocol | Service |
|---|---|---|
| 80 | TCP | HTTP — web UI + public file serving |
| 21 | TCP | FTP control |
| 40000–40009 | TCP | FTP passive data |
| 69 | UDP | TFTP |
To bind services to a specific interface (e.g., management VLAN only):
# docker-compose.yml
ports:
- "10.0.0.5:69:69/udp"
- "10.0.0.5:21:21"# 1. Upload firmware.bin through http://<host>/ (FileBrowser)
# Place it in /public/
# 2. From a network device (or test client):
tftp <host> -c get firmware.bin# From device or script — no credentials needed:
ftp -n <host> <<EOF
user anonymous
cd rw
put running-config.txt switch01-config.txt
bye
EOFftp -n <host> <<EOF
user admin changeme
cd public
mput *.bin
bye
EOFcurl -O http://<host>/public/firmware-v2.3.bin
wget -r -np http://<host>/public/# Change password
./set-password.sh 'N3w$ecure!Pass'
# Change username and password
./set-password.sh -u operator 'N3w$ecure!Pass'The script rewrites .env, updates FileBrowser config, and restarts affected containers.
netdrop/
├── docker-compose.yml
├── .env # credentials (gitignored)
├── .env.example # template
├── set-password.sh # credential management
├── data/
│ ├── public/ # read-only zone (HTTP/FTP/TFTP)
│ │ └── rw/ # writable zone (anon FTP/TFTP)
│ └── ... # private files (FileBrowser/auth FTP)
├── nginx/
│ └── nginx.conf
├── filebrowser/
│ └── config.yaml
├── vsftpd/
│ ├── Dockerfile
│ ├── entrypoint.sh
│ ├── vsftpd.conf
│ └── pam.d/vsftpd
└── tftpd/
├── Dockerfile
└── entrypoint.sh
- All services share a single
./data/bind mount — a file added through any protocol is immediately visible to all others - Write control in
/public/uses filesystem permissions, not protocol-level ACLs:/public/is owned byroot— read-only for FTP/TFTP service users/public/rw/is owned byftp/tftpuser — writable- Entrypoint scripts set ownership on every container start
- vsftpd uses PAM virtual users backed by Berkeley DB, built from env vars at startup
- FileBrowser password is injected via
FILEBROWSER_ADMIN_PASSWORDenv var (not in config file)
MIT