The per-IP ban tracker treats every globally-routable client equally, but an aggregating front-end like the finger-web proxy funnels the whole internet's federated lookups through a single IP. A burst from any one client of the proxy (or a load test) is then attributed to the proxy's IP and, once it crosses the failure threshold, the daemon blocks the proxy — taking out finger lookups for everyone. Per-client abuse protection for the proxied path belongs in the proxy (which now rate-limits per real client IP), so the daemon should trust it. Add a FINGER_BAN_ALLOWLIST env var (comma-separated IPs). Allowlisted addresses are marked non-trackable in the listener, so their connections are never blocked and never recorded as offenses. Unset = unchanged behaviour. - parse_ip_allowlist() in ban.cpp (trims entries, skips blanks) + unit tests - listener() consults the set when computing 'trackable' - documented in docker-compose.yml and DOCKER.md
11 KiB
Docker Setup and Deployment
This document describes how to build, run, and deploy the finger service using Docker and GitHub Actions.
Quick Start
Using Docker Compose (Recommended)
-
Clone the repository:
git clone https://github.com/waffle2k/finger.git cd finger -
Start the service:
docker compose up -d -
Test the service:
# Test with the example user finger john@localhost # Or using telnet telnet localhost 79 # Then type: john -
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
-
Build the image:
docker build -t finger-service . -
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:
-
Host networking (recommended). Add
network_mode: hostto the service (and drop theports:mapping -- it's ignored). The daemon then binds the host's port 79 directly and sees real client IPs. This is whatdocker-compose.ymlin this repo uses.Note: under host networking the container shares the host network namespace, which uses the host's privileged-port rule -- so the image's non-root user (UID 1000) cannot bind port 79 and the daemon fails to listen silently. Either run as root (
user: "0:0", as below) orsetcap cap_net_bind_service=+epon the binary in the image to keep it non-root.services: finger: image: ghcr.io/waffle2k/finger:latest network_mode: host user: "0:0" # bind privileged port 79 under host networking volumes: - ./users:/var/finger/users restart: unless-stopped -
macvlan network. Give the container its own IP on the LAN. More setup, but keeps the container off host networking.
-
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.
Allowlisting a trusted front-end (FINGER_BAN_ALLOWLIST)
Set FINGER_BAN_ALLOWLIST to a comma-separated list of client IPs that should
never be tracked or banned. This is for trusted aggregating front-ends: the
finger-web proxy, for example,
funnels every federated lookup through a single IP, so a burst from any one of
its clients would otherwise be attributed to the proxy and ban it for
everyone. Per-client abuse protection for that path lives in the proxy (it rate
limits per real client IP), so the daemon should trust the proxy IP:
environment:
- FINGER_BAN_ALLOWLIST=203.0.113.10,2001:db8::10
Addresses are matched verbatim against the connecting socket's address, so use canonical forms. Leave it unset for a directly-exposed daemon.
Docker Architecture
Multi-stage Build
The Dockerfile uses a multi-stage build approach:
-
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
-
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:
-
Build and Test:
- Builds the project with meson
- Runs all unit tests
- Uploads test results as artifacts
-
Multi-platform Docker Build:
- Builds for
linux/amd64andlinux/arm64 - Uses Docker Buildx for cross-platform support
- Implements build caching for faster builds
- Builds for
-
Container Registry Publishing:
- Publishes to GitHub Container Registry (
ghcr.io) - Tags with multiple strategies:
latestfor main branchv1.2.3for semantic version tagsmain-abc1234for commit SHApr-123for pull requests
- Publishes to GitHub Container Registry (
-
Security Scanning:
- Runs Trivy vulnerability scanner
- Uploads results to GitHub Security tab
- Fails on high-severity vulnerabilities
-
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
latesttag - 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 branchv1.0.0- Specific version releasesmain-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
- Mount your local
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
-
Build development image:
docker build --target builder -t finger-dev . -
Run tests in container:
docker run --rm finger-dev meson test -C builddir -
Interactive development:
docker run -it --rm \ -v $(pwd):/app \ -w /app \ finger-dev bash
Debugging
-
View container logs:
docker logs finger -
Execute into running container:
docker exec -it finger sh -
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
-
Permission denied on user files:
# Fix file permissions chmod 644 users/* -
Port 79 requires root:
# Use a different port docker run -p 8079:79 finger-service -
Container won't start:
# Check logs docker logs finger # Check if port is available netstat -ln | grep :79 -
Health check failing:
# Test manually docker exec finger nc -z localhost 79 # Check if service is running docker exec finger ps aux
Performance Tuning
-
Resource limits:
services: finger: deploy: resources: limits: memory: 64M cpus: '0.1' -
Connection limits:
- The service handles concurrent connections efficiently
- Default OS limits should be sufficient for most use cases
- Monitor with
docker statsfor resource usage
Security Considerations
-
Network Security:
- Finger protocol sends data in plain text
- Consider using behind a reverse proxy with TLS
- Restrict access with firewall rules
-
Data Security:
- User files are readable by the finger user
- Don't store sensitive information in status files
- Consider file permissions on the host
-
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:
- Test locally with
docker build - Ensure all tests pass in the container
- Update this documentation if needed
- The CI/CD pipeline will automatically test your changes
For more information, see the main README.md.