Files
finger/DOCKER.md
T
pmb 54650af252 Only track bannable (globally-routable) source IPs
The ban logic is per source IP, so it only works where the daemon can see
the real client. Behind Docker's default bridge networking every client is
SNAT'd to the bridge gateway (a 172.16/12 address), so a single IP would
stand in for the whole internet -- counting offenses against it would block
everyone at once.

Add is_bannable_address(): only globally-routable unicast addresses are
tracked. Loopback, RFC1918 private, CGNAT (100.64/10), link-local, IPv6
unique-local, and multicast all return false. main.cpp decides trackability
from the accepted endpoint and skips both the block check and offense
recording for non-global sources. Net effect: banning works where the real
IP is visible (FreeBSD jail via pf rdr; Docker with host networking) and is
inert -- not catastrophic -- where it is not (Docker bridge).

Document the Docker client-IP caveat: docker-compose.yml now defaults to
host networking, with the rationale and alternatives in DOCKER.md.
2026-06-15 16:38:07 -07:00

9.8 KiB

Docker Setup and Deployment

This document describes how to build, run, and deploy the finger service using Docker and GitHub Actions.

Quick Start

  1. Clone the repository:

    git clone https://github.com/waffle2k/finger.git
    cd finger
    
  2. Start the service:

    docker compose up -d
    
  3. Test the service:

    # Test with the example user
    finger john@localhost
    
    # Or using telnet
    telnet localhost 79
    # Then type: john
    
  4. Add your own users:

    # Create a status file for a user
    echo "Your status message here" > users/yourusername
    
    # Test it
    finger yourusername@localhost
    

Using Docker directly

  1. Build the image:

    docker build -t finger-service .
    
  2. Run the container:

    docker run -d \
      --name finger \
      -p 79:79 \
      -v $(pwd)/users:/var/finger/users \
      finger-service
    

Using Pre-built Images

You can also use the automatically built images from GitHub Container Registry:

docker run -d \
  --name finger \
  -p 79:79 \
  -v $(pwd)/users:/var/finger/users \
  ghcr.io/waffle2k/finger:latest

Abuse protection & client IPs (important)

The daemon bans source IPs that rack up repeated failed lookups (scanners, SIP/ HTTP probes, username guessers) -- see the "Abuse protection" section in the main README.md. That protection is per source IP, so it only works if the container can see the real client IP.

Under Docker's default bridge networking this is not the case: published ports are NAT'd so every external client arrives with the bridge gateway as its source (e.g. 172.20.0.1). The daemon would see one IP for the entire internet. By design it treats private/RFC1918 addresses as untrackable, so rather than blocking everyone at once, banning simply becomes inert under bridge networking.

To make abuse protection actually work in Docker, give the container the real client IP. In order of preference:

  1. Host networking (recommended). Add network_mode: host to the service (and drop the ports: mapping -- it's ignored). The daemon then binds the host's port 79 directly and sees real client IPs. Non-root bind of port 79 still works because Docker grants CAP_NET_BIND_SERVICE by default. This is what docker-compose.yml in this repo now uses.

    services:
      finger:
        image: ghcr.io/waffle2k/finger:latest
        network_mode: host
        volumes:
          - ./users:/var/finger/users
        restart: unless-stopped
    
  2. macvlan network. Give the container its own IP on the LAN. More setup, but keeps the container off host networking.

  3. Disable the userland proxy host-wide (/etc/docker/daemon.json: {"userland-proxy": false}, then restart dockerd). iptables DNAT then preserves the source IP on published ports. This is a host-wide change that restarts every container on the host -- avoid it on busy multi-service hosts.

Note: bans are in-memory, so they reset when the container restarts -- the same trade-off as any single-process deployment.

Docker Architecture

Multi-stage Build

The Dockerfile uses a multi-stage build approach:

  1. Builder Stage (Ubuntu 24.04):

    • Installs all build dependencies (meson, ninja, boost, gtest, etc.)
    • Compiles the C++20 source code
    • Runs all tests to ensure quality
    • Creates a statically linked binary
  2. Runtime Stage (Alpine Linux):

    • Minimal base image (~5MB)
    • Only includes runtime dependencies
    • Runs as non-root user for security
    • Includes health checks

Security Features

  • Non-root execution: Runs as user finger (UID 1000)
  • Minimal attack surface: Alpine Linux base with minimal packages
  • Health checks: Built-in container health monitoring
  • Read-only filesystem: Application doesn't write to filesystem

Image Size

  • Final image: ~15MB (Alpine + binary + minimal runtime deps)
  • Build image: ~2GB (includes all build tools, discarded after build)

GitHub Actions CI/CD

Automated Workflow

The repository includes a comprehensive GitHub Actions workflow (.github/workflows/docker-publish.yml) that:

  1. Build and Test:

    • Builds the project with meson
    • Runs all unit tests
    • Uploads test results as artifacts
  2. Multi-platform Docker Build:

    • Builds for linux/amd64 and linux/arm64
    • Uses Docker Buildx for cross-platform support
    • Implements build caching for faster builds
  3. Container Registry Publishing:

    • Publishes to GitHub Container Registry (ghcr.io)
    • Tags with multiple strategies:
      • latest for main branch
      • v1.2.3 for semantic version tags
      • main-abc1234 for commit SHA
      • pr-123 for pull requests
  4. Security Scanning:

    • Runs Trivy vulnerability scanner
    • Uploads results to GitHub Security tab
    • Fails on high-severity vulnerabilities
  5. Supply Chain Security:

    • Generates SLSA build provenance attestations
    • Signs container images
    • Provides build transparency

Triggering Builds

The workflow triggers on:

  • Push to main branch: Builds and publishes latest tag
  • Version tags: Builds and publishes semantic version tags (v1.0.0)
  • Pull requests: Builds but doesn't publish (security)

Using Published Images

Images are available at: ghcr.io/waffle2k/finger

Available tags:

  • latest - Latest stable build from main branch
  • v1.0.0 - Specific version releases
  • main-abc1234 - Specific commit builds

Configuration

Environment Variables

The container supports these environment variables:

  • FINGER_PORT: Port to listen on (default: 79)
  • FINGER_DATA_DIR: Directory for user files (default: /var/finger/users)

Volume Mounts

  • /var/finger/users: Directory containing user status files
    • Mount your local users/ directory here
    • Each file represents a user (filename = username)
    • File contents = user's status message

Health Checks

The container includes built-in health checks:

  • Check: TCP connection to port 79
  • Interval: Every 30 seconds
  • Timeout: 10 seconds
  • Retries: 3 attempts
  • Start period: 40 seconds

Development

Local Development with Docker

  1. Build development image:

    docker build --target builder -t finger-dev .
    
  2. Run tests in container:

    docker run --rm finger-dev meson test -C builddir
    
  3. Interactive development:

    docker run -it --rm \
      -v $(pwd):/app \
      -w /app \
      finger-dev bash
    

Debugging

  1. View container logs:

    docker logs finger
    
  2. Execute into running container:

    docker exec -it finger sh
    
  3. Check health status:

    docker inspect finger | grep -A 10 Health
    

Production Deployment

Docker Swarm

version: '3.8'
services:
  finger:
    image: ghcr.io/waffle2k/finger:latest
    ports:
      - "79:79"
    volumes:
      - finger_data:/var/finger/users
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    healthcheck:
      test: ["CMD", "nc", "-z", "localhost", "79"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  finger_data:

Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: finger-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: finger
  template:
    metadata:
      labels:
        app: finger
    spec:
      containers:
      - name: finger
        image: ghcr.io/waffle2k/finger:latest
        ports:
        - containerPort: 79
        volumeMounts:
        - name: user-data
          mountPath: /var/finger/users
        livenessProbe:
          tcpSocket:
            port: 79
          initialDelaySeconds: 30
          periodSeconds: 10
      volumes:
      - name: user-data
        configMap:
          name: finger-users
---
apiVersion: v1
kind: Service
metadata:
  name: finger-service
spec:
  selector:
    app: finger
  ports:
  - port: 79
    targetPort: 79
  type: LoadBalancer

Troubleshooting

Common Issues

  1. Permission denied on user files:

    # Fix file permissions
    chmod 644 users/*
    
  2. Port 79 requires root:

    # Use a different port
    docker run -p 8079:79 finger-service
    
  3. Container won't start:

    # Check logs
    docker logs finger
    
    # Check if port is available
    netstat -ln | grep :79
    
  4. Health check failing:

    # Test manually
    docker exec finger nc -z localhost 79
    
    # Check if service is running
    docker exec finger ps aux
    

Performance Tuning

  1. Resource limits:

    services:
      finger:
        deploy:
          resources:
            limits:
              memory: 64M
              cpus: '0.1'
    
  2. Connection limits:

    • The service handles concurrent connections efficiently
    • Default OS limits should be sufficient for most use cases
    • Monitor with docker stats for resource usage

Security Considerations

  1. Network Security:

    • Finger protocol sends data in plain text
    • Consider using behind a reverse proxy with TLS
    • Restrict access with firewall rules
  2. Data Security:

    • User files are readable by the finger user
    • Don't store sensitive information in status files
    • Consider file permissions on the host
  3. Container Security:

    • Runs as non-root user
    • Uses minimal base image
    • Regular security scanning in CI/CD
    • Keep images updated

Contributing

When contributing Docker-related changes:

  1. Test locally with docker build
  2. Ensure all tests pass in the container
  3. Update this documentation if needed
  4. The CI/CD pipeline will automatically test your changes

For more information, see the main README.md.