kaczmar2/tftp-server

By kaczmar2

Updated 17 days ago

A minimal, secure TFTP server with optional web server based on Alpine Linux.

Image
Networking
2

3.5K

kaczmar2/tftp-server repository overview

TFTP Server Docker Image

Docker Build, Test and Publish

A minimal, secure TFTP server with optional web server based on Alpine Linux, tftpd-hpa, and BusyBox httpd.

Features

  • TFTP-only mode: Minimal TFTP server for network boot scenarios
  • TFTP + Web mode: TFTP server with HTTP access to boot files and scripts
  • Runtime mode selection: Single image, choose mode with environment variable
  • Multi-architecture: Supports AMD64, ARM64, and ARM v7
  • Security: Runs as nobody user with proper privilege dropping
  • Logging: Unified Docker logs for both TFTP and HTTP activity

Images, Tags, and Platforms

The same image is published to two registries:

docker pull kaczmar2/tftp-server            # Docker Hub
docker pull ghcr.io/kaczmar2/tftp-server    # GitHub Container Registry

Available tags:

  • latest — the most recent release
  • Version tags such as 1.4.1 — a fixed release

For a stable system, pin a version tag. The latest tag moves whenever the Alpine base image receives a security update.

Supported platforms: linux/amd64, linux/arm64, linux/arm/v7.

Note on the examples below: they use the GHCR name, because that is what the docker-compose.yml in the repository uses. Both registries carry identical images, so you can replace ghcr.io/kaczmar2/tftp-server with kaczmar2/tftp-server anywhere you see it.

Quick Start

# Clone or create the project directory
mkdir -p ~/docker/tftp-server && cd ~/docker/tftp-server

# Download docker-compose.yml
curl -O https://raw.githubusercontent.com/kaczmar2/tftp-server/main/docker-compose.yml

# Create .env file for configuration (optional)
curl -O https://raw.githubusercontent.com/kaczmar2/tftp-server/main/.env.example
cp .env.example .env
# Edit .env to set TZ, TFTP_ROOT, and WEB_ROOT if needed

# Create Docker bind mount directories
sudo mkdir -p /srv/docker/tftp /srv/docker/www

# Start TFTP + Web server (default)
docker compose up -d

# OR start TFTP-only mode
docker compose --profile tftp-only up -d

# Check status
docker compose ps
docker logs tftp-server
Using Docker Run
# TFTP + Web server mode
docker run -d \
  --name tftp-server \
  --network host \
  --restart unless-stopped \
  -e TZ=America/Denver \
  -e ENABLE_WEBSERVER=true \
  -v /srv/docker/tftp:/srv/tftp \
  -v /srv/docker/www:/srv/www \
  ghcr.io/kaczmar2/tftp-server

# TFTP-only mode
docker run -d \
  --name tftp-server \
  --network host \
  --restart unless-stopped \
  -e TZ=America/Denver \
  -e ENABLE_WEBSERVER=false \
  -v /srv/docker/tftp:/srv/tftp \
  ghcr.io/kaczmar2/tftp-server

Configuration

Environment Variables
  • ENABLE_WEBSERVER: Set to true to enable HTTP server, false for TFTP-only (default: false)
  • WEB_PORT: TCP port for the HTTP server (default: 80). Only used when ENABLE_WEBSERVER=true.
  • TZ: Timezone for logs and timestamps (default: UTC)
  • TFTP_ARGS: Custom TFTP daemon arguments (see Custom TFTP Options section)

Note: The container uses host networking, so WEB_PORT is the port on the host. The supplied .env.example sets WEB_PORT=8080 to avoid a conflict with an existing web server on port 80. Change it to 80 if port 80 is free. The examples in this document assume port 80.

Docker Compose Profiles
  • Default (docker compose up): TFTP + Web server mode
  • tftp-only (docker compose --profile tftp-only up): TFTP-only mode
Complete Docker Compose Example
services:
  # TFTP-only server
  tftp-only:
    container_name: tftp-server
    image: ghcr.io/kaczmar2/tftp-server
    restart: unless-stopped
    network_mode: host
    environment:
      - TZ=${TZ:-UTC}
      - ENABLE_WEBSERVER=false
    volumes:
      - ${TFTP_ROOT:-/srv/docker/tftp}:/srv/tftp
    profiles:
      - tftp-only

  # TFTP + BusyBox httpd web server (default)
  tftp-web:
    container_name: tftp-server
    image: ghcr.io/kaczmar2/tftp-server
    restart: unless-stopped
    network_mode: host
    environment:
      - TZ=${TZ:-UTC}
      - ENABLE_WEBSERVER=true
      - WEB_PORT=${WEB_PORT:-80}
    volumes:
      - ${TFTP_ROOT:-/srv/docker/tftp}:/srv/tftp
      - ${WEB_ROOT:-/srv/docker/www}:/srv/www

The tftp-web service has no profiles key, so docker compose up starts it. The tftp-only service starts only when you ask for its profile.

Directory Structure

TFTP Files
/srv/docker/tftp/         # Host directory (mapped to container /srv/tftp)
├── bootfile.txt          # File to serve via TFTP
├── firmware.bin          # File to serve via TFTP
└── subdirectory/         # Subdirectories are supported
    └── nested-file.txt
Web Files (when ENABLE_WEBSERVER=true)
/srv/docker/www/          # Host directory (mapped to container /srv/www)
├── index.html            # Served via HTTP at http://server/
├── boot-scripts/         # Directory listing available
│   ├── script1.sh        # Served via HTTP at http://server/boot-scripts/script1.sh
│   └── script2.py
└── documentation/
    └── readme.txt

Usage

Testing TFTP Access

Install a TFTP client to test your server:

# Install TFTP client
sudo apt install tftp-hpa

Test file download:

cd /tmp
uname -a | sudo tee /srv/docker/tftp/test
tftp localhost
tftp> get test
tftp> quit
diff test /srv/docker/tftp/test
# (no output = files are identical)
Testing Web Server Access (ENABLE_WEBSERVER=true)
# Create test content
echo "<h1>TFTP Boot Server</h1>" | sudo tee /srv/docker/www/index.html
echo "#!/bin/bash\necho 'Boot script executed'" | sudo tee /srv/docker/www/boot.sh

# Test HTTP access
curl http://localhost/                    # Should show HTML
curl http://localhost/boot.sh             # Should show script
curl -I http://localhost/                 # Check headers
Viewing Logs
# Real-time logs (includes TFTP and HTTP requests)
docker logs -f tftp-server

# Check for TFTP requests (RRQ = Read Request)
docker logs tftp-server | grep RRQ

# Check for TFTP errors (NAK = Negative Acknowledgment)
docker logs tftp-server | grep NAK

# Check for HTTP requests (when web server enabled)
# BusyBox httpd logs show client IP, port, and response code
docker logs tftp-server | grep "response:"

Log examples:

# TFTP requests (detailed)
<29>Jan 16 10:30:15 in.tftpd[25]: RRQ from 192.168.1.100 filename bootfile.txt

# HTTP requests (minimal format - IP, port, status only)
[::ffff:10.10.10.144]:49118: response:200
[::ffff:10.10.10.145]:46906: response:404

# Service status
Starting TFTP server with process supervisor...
Web server enabled - HTTP accessible on port 80 (PID: 16)
TFTP server started (PID: 17)
HTTP Log Format

BusyBox httpd provides minimal access logging with the following format:

[<client_ip>]:<client_port>: response:<status_code>

Field Breakdown:

  • Client IP: IPv6-mapped IPv4 address (e.g., ::ffff:10.10.10.144) or IPv6 address
  • Client Port: Ephemeral port used by the client (e.g., 49118)
  • Status Code: HTTP response code (e.g., 200, 404, 403)

Common HTTP Status Codes:

  • 200 - Success (file found and served)
  • 404 - Not Found (requested file doesn't exist)
  • 403 - Forbidden (permission denied)
  • 304 - Not Modified (cached response)
  • 500 - Internal Server Error

Limitations:

  • ❌ No timestamps (use container logs or correlate with TFTP timestamps)
  • ❌ No request method (GET, POST, etc.)
  • ❌ No requested file path or URL
  • ❌ No bytes transferred or user agent
  • ✅ Minimal overhead and ultra-lightweight

Workaround for file tracking: If you need to know which files are being accessed via HTTP, monitor file access times on the host:

# Check which files were recently accessed
ls -ltu /srv/docker/www/

Note: For production environments requiring detailed access logs, consider using a dedicated web server like lighttpd or nginx instead of BusyBox httpd.

File Management
# Add TFTP files
cp bootfile.txt /srv/docker/tftp/
chmod 644 /srv/docker/tftp/*

# Add web files (when using web server)
cp index.html /srv/docker/www/
cp -r boot-scripts/ /srv/docker/www/
chmod 644 /srv/docker/www/* /srv/docker/www/**/*

# Check what files are available
ls -la /srv/docker/tftp/     # TFTP files
ls -la /srv/docker/www/      # Web files (if enabled)

Network Requirements

Host Networking

This container requires network_mode: host because:

  • TFTP uses dynamic ports - Data transfers use random ephemeral ports
  • Port mapping doesn't work - Docker can't map unknown future ports
  • Host networking is standard - Most TFTP Docker images use this approach
Firewall

TFTP (always required):

  • UDP port 69 must be accessible

Web server (when ENABLE_WEBSERVER=true):

  • The TCP port set by WEB_PORT must be accessible (default: 80)

Custom TFTP Options

The container supports customizing TFTP daemon behavior via the TFTP_ARGS environment variable. You can pass any valid in.tftpd options while keeping the current defaults as the base.

Docker Run Example
# Start container with --create flag to enable uploads
docker run \
  --network host \
  -e ENABLE_WEBSERVER=false \
  -e TFTP_ARGS="--foreground --secure --create --verbosity 4 --user nobody" \
  -v /srv/docker/tftp:/srv/tftp \
  ghcr.io/kaczmar2/tftp-server
Docker Compose Examples

Add to your .env file:

# Enable write access with custom settings
TFTP_ARGS=--foreground --secure --create --verbosity 4 --user nobody
Available Options

See the tftpd man page for all available options.

Limitations

When customizing TFTP_ARGS, note these restrictions:

  • Required options: Always include --foreground --user nobody for proper container operation and security
  • Conflicting options: Don't use --listen as it conflicts with --foreground (required for containers)
  • Security: Avoid changing --user from nobody as this breaks the container's security model
  • Directory: The TFTP root directory is fixed to /srv/tftp and cannot be changed via arguments
File Permissions for Uploads

Note: Setting up host directory permissions for TFTP uploads is beyond the scope of this document, as requirements vary by environment.

For general guidance when using --create: the container process needs write access to the mounted directory. This typically involves setting appropriate permissions on the host directory before starting the container.

License

MIT. See the LICENSE file.

Tag summary

Content type

Image

Digest

sha256:8611caa3b

Size

4.4 MB

Last updated

17 days ago

docker pull kaczmar2/tftp-server