Skip to content

Create hidden services for your containers - everything in docker.

License

Notifications You must be signed in to change notification settings

hundehausen/tor-hidden-service-docker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

40 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Tor Hidden Service Docker

Docker Build Tor Version Alpine Version

A lightweight, secure Docker container for running Tor hidden services. Built on Alpine Linux with a focus on minimal footprint and maximum security.

Table of Contents

πŸš€ Quick Start

# Run with a simple web service
docker run -d --name my-tor-service \
  -e HS_WEB=web:80:80 \
  ghcr.io/hundehausen/tor-hidden-service:latest

# View your .onion address
docker logs my-tor-service

Or use Docker Compose:

git clone https://github.com/hundehausen/tor-hidden-service-docker.git
cd tor-hidden-service-docker
docker compose up -d
docker compose logs -f tor

✨ Features

  • πŸͺΆ Minimal footprint - Alpine Linux base (~44MB)
  • πŸŽ›οΈ Easy hidden services - Configure via environment variables
  • πŸ”’ Security-first - Runs as non-root user with minimal privileges
  • πŸ›‘οΈ Input validation - Defense-in-depth against path traversal and injection
  • πŸ”Ž Auto-discovery - Onion addresses displayed in logs
  • πŸ”‘ Key persistence - Reuse existing keys across restarts
  • πŸ’“ Health checks - Built-in container health monitoring
  • πŸ›‘ Graceful shutdown - Proper SIGTERM/SIGINT handling with 30s timeout
  • πŸ—οΈ Multi-arch - Supports linux/amd64 and linux/arm64

βš™οΈ How It Works

  1. The container runs a Tor daemon that registers your services on the Tor network
  2. Each HS_* environment variable creates a hidden service with its own .onion address
  3. Incoming connections to a .onion address are forwarded to the specified target container and port
  4. A SOCKS proxy on port 9050 is available for outbound Tor connections

πŸ“¦ Usage

Basic Usage

docker run -d --name tor-hidden-service \
  -e HS_WEB=web-container:80:80 \
  ghcr.io/hundehausen/tor-hidden-service:latest

Docker Compose

services:
  web:
    image: nginx:alpine
    container_name: web-container
    volumes:
      - ./example-site:/usr/share/nginx/html
    networks:
      - tor-network

  tor:
    image: ghcr.io/hundehausen/tor-hidden-service:latest
    container_name: tor-hidden-service
    environment:
      - HS_WEB=web:80:80
      - SOCKS_BIND=127.0.0.1
    volumes:
      - tor-data:/var/lib/tor
    networks:
      - tor-network
    cap_drop:
      - ALL
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: '0.5'

networks:
  tor-network:
    driver: bridge

volumes:
  tor-data:

Retrieving Onion Addresses

From container logs:

docker logs tor-hidden-service

Output:

======== TOR HIDDEN SERVICES ========
WEB: 2xxiyj6noereyty4xdxjg5akcop7ylnotvf4fqre57g7xuppy4tixvqd.onion
API: 7gtpkyhhhsbowew7zha6z7h7vlffqkn3nbu2f6hgin4xddq22o7yytyd.onion
====================================

From the filesystem:

# Get specific service address
docker exec tor-hidden-service cat /var/lib/tor/WEB/hostname

# List all services
docker exec tor-hidden-service ls /var/lib/tor/

πŸ”§ Configuration

Environment Variables

Configure hidden services using the format:

HS_[SERVICE_NAME]=[TARGET_HOST]:[TARGET_PORT]:[VIRTUAL_PORT]
Variable Description Example
SERVICE_NAME Unique identifier (alphanumeric, hyphens, underscores) WEB, API, BLOG
TARGET_HOST Hostname or container name web, api.internal
TARGET_PORT Port the service listens on 80, 8080
VIRTUAL_PORT (Optional) Port exposed on .onion address, defaults to TARGET_PORT 80

Examples:

# Single service
HS_WEB=web-container:80:80

# Multiple services (each gets a unique .onion address)
HS_WEB=web:80:80
HS_API=api:8080:80
HS_BLOG=blog:3000:80

SOCKS Proxy

The container exposes a SOCKS5 proxy on port 9050 for routing traffic through Tor.

By default, the proxy binds to 0.0.0.0 (all interfaces). For production, restrict this to localhost:

environment:
  - SOCKS_BIND=127.0.0.1

Note: The default will change to 127.0.0.1 in a future major version.

Command-Line Arguments

You can also pass services as arguments:

docker run -d \
  ghcr.io/hundehausen/tor-hidden-service:latest \
  web:web-container:80:80 \
  api:api-container:8080:80

πŸ›‘οΈ Security

Hardening Checklist

  • 🚫 Drop capabilities - Add cap_drop: [ALL] to your compose file
  • πŸ“Š Set resource limits - Prevent resource exhaustion (see Usage)
  • πŸ” Restrict SOCKS proxy - Set SOCKS_BIND=127.0.0.1 in production
  • πŸ’Ύ Persist keys securely - Mount /var/lib/tor as a volume, never commit keys to git

Protecting Your Keys

The hs_ed25519_secret_key files in /var/lib/tor/[service]/ are the cryptographic identity of your .onion address. If compromised, an attacker can impersonate your service.

⚠️ Never commit private keys to version control.

Input Validation

The entrypoint script validates all inputs:

  • Service names - Alphanumeric, hyphens, and underscores only (max 64 chars)
  • Ports - Numeric, range 1-65535
  • Hostnames - No shell metacharacters allowed
  • Path traversal - Prevented via allowlist and path verification

πŸ”‘ Key Persistence

Option 1: Full Directory Persistence

volumes:
  - tor-data:/var/lib/tor

Option 2: Selective Key Reuse

volumes:
  - tor-data:/var/lib/tor
  - ./backup-keys/WEB:/var/lib/tor/WEB  # Restore specific service
  - ./backup-keys/API:/var/lib/tor/API

Backing Up Keys

# Copy keys from running container
docker cp tor-hidden-service:/var/lib/tor/WEB ./backup-keys/

# Or archive the entire volume
docker run --rm -v tor-data:/data -v $(pwd):/backup alpine \
  tar czf /backup/tor-keys.tar.gz -C /data .

πŸ“š Examples

Static Website

services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./website:/usr/share/nginx/html:ro
    networks:
      - tor

  tor:
    image: ghcr.io/hundehausen/tor-hidden-service:latest
    environment:
      - HS_SITE=nginx:80:80
      - SOCKS_BIND=127.0.0.1
    volumes:
      - tor-keys:/var/lib/tor
    cap_drop:
      - ALL
    networks:
      - tor

networks:
  tor:
volumes:
  tor-keys:

Multiple Services

services:
  web:
    image: nginx:alpine
    networks:
      - tor

  api:
    image: node:18-alpine
    networks:
      - tor

  tor:
    image: ghcr.io/hundehausen/tor-hidden-service:latest
    environment:
      - HS_WEBSITE=web:80:80
      - HS_API=api:3000:80
      - HS_ADMIN=api:8080:80
      - SOCKS_BIND=127.0.0.1
    volumes:
      - tor-data:/var/lib/tor
    cap_drop:
      - ALL
    networks:
      - tor

networks:
  tor:
volumes:
  tor-data:

πŸ’“ Health Checks

The container includes a health check that verifies Tor network connectivity:

  • Interval: 300 seconds (5 minutes)
  • Timeout: 3 seconds
  • Test: Connects to check.torproject.org via SOCKS proxy

Check health status:

docker ps --filter name=tor-hidden-service --format "table {{.Names}}\t{{.Status}}"

πŸ” Troubleshooting

Can't connect to hidden service

  1. Verify Tor bootstrap: docker logs tor-hidden-service | grep "Bootstrapped 100%"
  2. Check service configuration: docker exec tor-hidden-service cat /etc/tor/torrc
  3. Ensure target container is reachable: docker exec tor-hidden-service ping web

Onion address keeps changing

Keys are not being persisted. Add a volume for /var/lib/tor:

volumes:
  - tor-data:/var/lib/tor

High memory usage

Set resource limits in your compose file:

deploy:
  resources:
    limits:
      memory: 256M

🀝 Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.


Disclaimer: This tool is for legitimate privacy purposes only. Users are responsible for complying with all applicable laws and regulations in their jurisdiction.

About

Create hidden services for your containers - everything in docker.

Topics

Resources

License

Stars

Watchers

Forks

Packages