Sign inSign up

kaczmar2/tftp-server

By kaczmar2

•Updated 14 days ago

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

Image
Networking
2

10K+

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:5b4cc79a3…

Size

4.3 MB

Last updated

14 days ago

docker pull kaczmar2/tftp-server