Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

83 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
    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

Custom torrc directives

Two env vars accept multi-line torrc content, appended to /etc/tor/torrc at boot:

Variable Where it lands Use for
TORRC After SocksPort, before any hidden service section Global directives
HSTORRC_<SERVICE> After that service's HiddenServicePort line Per-service directives (PoW, IntroDoS, MaxStreams, …)

Hyphens in service names become underscores (HS_MY-API β†’ HSTORRC_MY_API). Tor validates directives at startup; bad config fails fast in the logs.

services:
  tor:
    image: ghcr.io/hundehausen/tor-hidden-service:latest
    environment:
      SOCKS_BIND: 127.0.0.1
      HS_WEB: web:80:80
      HS_API: api:8080:80

      TORRC: |
        ClientUseIPv6 1

      HSTORRC_WEB: |
        HiddenServicePoWDefensesEnabled 1
        HiddenServicePoWQueueRate 250
        HiddenServicePoWQueueBurst 1000
        HiddenServiceEnableIntroDoSDefense 1
        HiddenServiceEnableIntroDoSRatePerSec 25
        HiddenServiceEnableIntroDoSBurstPerSec 200

      HSTORRC_API: |
        HiddenServiceMaxStreams 50
        HiddenServiceMaxStreamsCloseCircuit 1

Inspect the rendered config:

docker exec tor-hidden-service cat /etc/tor/torrc

πŸ›‘οΈ Security

Hardening Checklist

  • πŸ“Š 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
    networks:
      - tor

networks:
  tor:
volumes:
  tor-keys:

Multiple Services

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

  api:
    image: node:24-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
    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}}"

πŸ” Supply Chain

Every published image on ghcr.io/hundehausen/tor-hidden-service ships with three attestations:

  • SBOM (Software Bill of Materials, SPDX 2.3) β€” every package inside the image
  • Provenance (SLSA v0.2) β€” how the image was built, from which commit, by which CI
  • Sigstore signature β€” keyless, signed via GitHub OIDC, recorded in Rekor

Verifying the signature

Requires cosign:

cosign verify ghcr.io/hundehausen/tor-hidden-service:latest \
  --certificate-identity-regexp 'https://github\.com/hundehausen/tor-hidden-service-docker/\.github/workflows/.+' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Exit code 0 and a JSON payload listing one signature means the image was built by this repo's CI β€” not a third-party reupload.

Inspecting the SBOM

docker buildx imagetools inspect ghcr.io/hundehausen/tor-hidden-service:latest \
  --format '{{ json (index .SBOM "linux/amd64").SPDX }}'

Returns SPDX JSON listing every Alpine package and version inside the image.

Inspecting provenance

docker buildx imagetools inspect ghcr.io/hundehausen/tor-hidden-service:latest \
  --format '{{ json .Provenance }}'

Returns SLSA v0.2 JSON with the source repo URL, commit SHA, builder version, and platform. Confirms the image came from this repo's main branch at a specific commit.

πŸ” 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

Stars

30 stars

Watchers

2 watching

Forks

Packages

Used by

Contributors

Languages