A fully configurable SSH server Docker container designed specifically for integration testing, development, and SSH client validation. Built with security best practices and complete runtime configurability.
Available in two optimized variants:
- Debian-based (
ghcr.io/billchurch/ssh_test:debian): 118MB - Maximum compatibility with full GNU toolchain - Alpine-based (
ghcr.io/billchurch/ssh_test:alpine): 13.8MB - Ultra-minimal footprint for resource-constrained environments
- π§ Fully Configurable: All SSH settings configurable via environment variables
- π Multiple Auth Methods: Password, public key, and keyboard-interactive authentication
- ποΈ Multi-Architecture: Supports AMD64 and ARM64 architectures
- π‘οΈ Security Focused: Configurable security hardening options
- π Debug Support: Adjustable SSH debug levels with proper logging
- π CI/CD Ready: Automated builds, testing, and container registry publishing
- π§ͺ Testing Tools: Comprehensive test scripts and integration tests included
Choose the image variant that best fits your needs:
# Alpine - Ultra-minimal (13.8MB)
docker pull ghcr.io/billchurch/ssh_test:alpine
# Debian - Full compatibility (118MB, default)
docker pull ghcr.io/billchurch/ssh_test:debian
# or
docker pull ghcr.io/billchurch/ssh_test:latest # same as debian# Basic password authentication (Alpine - minimal)
docker run -d --name ssh-test-alpine \
-p 2225:22 \
-e SSH_USER=testuser \
-e SSH_PASSWORD=testpass123 \
ghcr.io/billchurch/ssh_test:alpine
# Basic password authentication (Debian - full compatibility)
docker run -d --name ssh-test-debian \
-p 2224:22 \
-e SSH_USER=testuser \
-e SSH_PASSWORD=testpass123 \
ghcr.io/billchurch/ssh_test:debian
# Test the connections
ssh -p 2225 testuser@localhost # Alpine
ssh -p 2224 testuser@localhost # Debian# Clone the repository
git clone https://github.com/billchurch/ssh-test-server.git
cd ssh-test-server/examples
# Run Alpine version (minimal)
docker-compose --profile alpine up -d
# Run Debian version (full compatibility)
docker-compose --profile debian up -d
# Run both versions simultaneously
docker-compose --profile all up -d
# Traditional profiles still available
docker-compose --profile basic up -d # Basic password auth
docker-compose --profile pubkey up -d # Public key auth only
docker-compose --profile hardened up -d # Security hardenedAll configuration is done through environment variables:
| Variable | Default | Description |
|---|---|---|
SSH_USER |
testuser |
SSH username to create |
SSH_PASSWORD |
(empty) | Password for the SSH user |
SSH_PORT |
22 |
SSH server port |
SSH_DEBUG_LEVEL |
0 |
Debug level (0=none, 1=verbose, 2=debug, 3=debug3) |
| Variable | Default | Description |
|---|---|---|
SSH_PERMIT_PASSWORD_AUTH |
yes |
Enable password authentication |
SSH_PERMIT_PUBKEY_AUTH |
yes |
Enable public key authentication |
SSH_CHALLENGE_RESPONSE_AUTH |
no |
Enable keyboard-interactive auth |
SSH_AUTHORIZED_KEYS |
(empty) | SSH public keys (newline separated) |
SSH_AUTH_METHODS |
any |
Specific auth methods to require |
| Variable | Default | Description |
|---|---|---|
SSH_PERMIT_ROOT_LOGIN |
no |
Allow root login |
SSH_PERMIT_EMPTY_PASSWORDS |
no |
Allow empty passwords |
SSH_MAX_AUTH_TRIES |
6 |
Maximum authentication attempts |
SSH_LOGIN_GRACE_TIME |
120 |
Login grace time in seconds |
SSH_USE_DNS |
no |
Perform DNS lookups |
| Variable | Default | Description |
|---|---|---|
SSH_X11_FORWARDING |
no |
Enable X11 forwarding |
SSH_AGENT_FORWARDING |
no |
Enable SSH agent forwarding |
SSH_TCP_FORWARDING |
no |
Enable TCP forwarding |
The SSH test server includes comprehensive SSH agent support for testing agent-based authentication and forwarding scenarios.
| Variable | Default | Description |
|---|---|---|
SSH_AGENT_START |
no |
Start internal SSH agent in container |
SSH_AGENT_KEYS |
(empty) | Base64-encoded private keys to load into agent (newline-separated) |
SSH_AGENT_SOCKET_PATH |
/tmp/ssh-agent.sock |
Custom path for SSH agent socket |
- Internal Agent: Start an SSH agent inside the container with
SSH_AGENT_START=yes - Key Loading: Automatically load private keys into the agent using
SSH_AGENT_KEYS - Agent Forwarding: Allow client agents to be forwarded with
SSH_AGENT_FORWARDING=yes - Custom Socket: Use custom socket paths for testing specific scenarios
- Environment Setup: Automatically configures user environment for agent access
SSH_AGENT_KEYS variable contains sensitive private key data. In production or CI/CD environments:
- Load keys from secure environment variables or secrets management
- Never commit private keys to version control
- Use temporary keys for testing when possible
- Consider using key passphrases for additional security
| Variable | Default | Description |
|---|---|---|
SSH_HOST_KEYS |
(auto-generated) | Custom host keys (base64 encoded) |
SSH_CUSTOM_CONFIG |
(empty) | Additional sshd_config directives |
SSH_USE_PAM |
no |
Use PAM for authentication |
The SSH_CUSTOM_CONFIG environment variable allows you to add any additional SSH configuration directives that aren't explicitly handled by other environment variables. Multiple directives can be stacked using newlines.
# Allow specific environment variables from SSH client
docker run -d \
-p 2244:22 \
-e SSH_USER=testuser \
-e SSH_PASSWORD=testpassword \
-e SSH_AUTHORIZED_KEYS="$(cat ./test-keys/*.pub)" \
-e SSH_DEBUG_LEVEL=3 \
-e SSH_PERMIT_PASSWORD_AUTH=yes \
-e SSH_PERMIT_PUBKEY_AUTH=yes \
-e SSH_CUSTOM_CONFIG=$'PermitUserEnvironment yes\nAcceptEnv FOO' \
ghcr.io/billchurch/ssh_test:alpine
# Multiple custom configurations
docker run -d \
-e SSH_CUSTOM_CONFIG=$'MaxSessions 10\nClientAliveInterval 30\nClientAliveCountMax 3' \
ghcr.io/billchurch/ssh_test:alpinedocker run -d --name ssh-password-test \
-p 2224:22 \
-e SSH_USER=testuser \
-e SSH_PASSWORD=secure123 \
-e SSH_PERMIT_PASSWORD_AUTH=yes \
-e SSH_PERMIT_PUBKEY_AUTH=no \
ghcr.io/billchurch/ssh_test:latest# Generate a test key
ssh-keygen -t ed25519 -f test_key -N ""
# Start container with public key
docker run -d --name ssh-key-test \
-p 2224:22 \
-e SSH_USER=keyuser \
-e "SSH_AUTHORIZED_KEYS=$(cat test_key.pub)" \
-e SSH_PERMIT_PASSWORD_AUTH=no \
-e SSH_PERMIT_PUBKEY_AUTH=yes \
ghcr.io/billchurch/ssh_test:latest
# Connect with the key
ssh -i test_key -p 2224 keyuser@localhostNote: When no password is provided (SSH_PASSWORD not set), the user account is automatically unlocked by setting a dummy password hash (*) to enable SSH key authentication while still preventing password logins.
docker run -d --name ssh-secure-test \
-p 2224:22 \
-e SSH_USER=secureuser \
-e SSH_PASSWORD=VerySecureP@ss123 \
-e SSH_PERMIT_ROOT_LOGIN=no \
-e SSH_MAX_AUTH_TRIES=3 \
-e SSH_LOGIN_GRACE_TIME=60 \
-e SSH_USE_DNS=no \
-e SSH_X11_FORWARDING=no \
-e SSH_AGENT_FORWARDING=no \
-e SSH_TCP_FORWARDING=no \
ghcr.io/billchurch/ssh_test:latestdocker run -d --name ssh-debug-test \
-p 2224:22 \
-e SSH_USER=debuguser \
-e SSH_PASSWORD=debugpass \
-e SSH_DEBUG_LEVEL=3 \
ghcr.io/billchurch/ssh_test:latest
# View debug logs
docker logs -f ssh-debug-testdocker run -d --name ssh-agent-test \
-p 2224:22 \
-e SSH_USER=agentuser \
-e SSH_PASSWORD=agentpass \
-e SSH_AGENT_START=yes \
-e SSH_AGENT_FORWARDING=yes \
ghcr.io/billchurch/ssh_test:latest# Generate a test key
ssh-keygen -t ed25519 -f test_agent_key -N "" -C "test@example.com"
# Encode the private key for the container
KEY_DATA=$(base64 -i test_agent_key | tr -d '\n')
# Run container with agent and key
docker run -d --name ssh-agent-keys \
-p 2224:22 \
-e SSH_USER=keyuser \
-e SSH_AUTHORIZED_KEYS="$(cat test_agent_key.pub)" \
-e SSH_PERMIT_PASSWORD_AUTH=no \
-e SSH_AGENT_START=yes \
-e SSH_AGENT_KEYS="${KEY_DATA}" \
ghcr.io/billchurch/ssh_test:latest
# Test SSH connection using the agent
ssh -p 2224 keyuser@localhost
# Cleanup
rm -f test_agent_key test_agent_key.pub# For testing client agent forwarding
docker run -d --name ssh-forwarding-test \
-p 2224:22 \
-e SSH_USER=forwarduser \
-e SSH_PASSWORD=forwardpass \
-e SSH_AGENT_FORWARDING=yes \
-e SSH_AGENT_START=no \
ghcr.io/billchurch/ssh_test:latest
# Connect with agent forwarding enabled
ssh -A -p 2224 forwarduser@localhostThe project includes comprehensive testing tools:
# Test password authentication
./scripts/test-connection.sh --host localhost --port 2224 --user testuser --password testpass123
# Test public key authentication
./scripts/test-connection.sh --host localhost --port 2224 --user testuser --key ~/.ssh/id_rsa
# Verbose mode
./scripts/test-connection.sh --host localhost --port 2224 --user testuser --password testpass123 --verbose# Test against a running container
./scripts/test-auth-methods.sh --container ssh-test-server --user testuser
# Generate test keys and run comprehensive tests
./scripts/test-auth-methods.sh --container ssh-test-server --user testuser --generate-keys --cleanup-keys# Run full integration tests
./tests/integration/run-tests.sh
# Run tests with custom image
./tests/integration/run-tests.sh --image your-custom-image:latest
# Run tests in parallel with reporting
./tests/integration/run-tests.sh --parallel --report test-results.txt# Build both variants
make build-all
# Build specific variants
make build-debian # Debian-based image
make build-alpine # Alpine-based image
# Build for local testing
make build-dev # Development version (Debian)
# Build multi-architecture (requires buildx)
docker buildx build --platform linux/amd64,linux/arm64 -f docker/Dockerfile -t ssh-test-server:multi .# Test both image variants
make test-all
# Test specific variants
make test-debian # Test Debian image
make test-alpine # Test Alpine image
# Run connection tests
make test-connection-debian # Test Debian container connection
make test-connection-alpine # Test Alpine container connection
# Run authentication tests
make test-auth-debian # Test Debian auth methods
make test-auth-alpine # Test Alpine auth methods
# Compare image sizes
make compare-sizes
# Full integration test with build
make integration-test # Build both + test bothWe welcome contributions! This project uses automated releases and conventional commit messages.
Quick Start:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Make changes following our contribution guidelines
- Use conventional commit messages:
feat: add new feature - Run tests:
./tests/integration/run-tests.sh - Submit a pull request
Important: Please read our CONTRIBUTING.md for detailed guidelines on:
- Conventional commit message format
- Development workflow
- Testing requirements
- Release process
- Code style guidelines
The project uses GitHub Actions for:
- Multi-architecture builds for AMD64 and ARM64
- Automated testing with comprehensive integration tests
- Security scanning with Trivy vulnerability scanner
- Container publishing to GitHub Container Registry
- SBOM generation for supply chain security
Images are automatically published to GitHub Container Registry:
# Latest stable release (Debian)
docker pull ghcr.io/billchurch/ssh_test:latest
# Specific version (Debian)
docker pull ghcr.io/billchurch/ssh_test:v1.0.2
# Specific version with variant
docker pull ghcr.io/billchurch/ssh_test:v1.0.2-alpine
docker pull ghcr.io/billchurch/ssh_test:v1.0.2-debian
# Latest variant tags
docker pull ghcr.io/billchurch/ssh_test:alpine
docker pull ghcr.io/billchurch/ssh_test:debian
# Development builds
docker pull ghcr.io/billchurch/ssh_test:mainPerfect for testing SSH clients, automation tools, and deployment scripts:
# Start test environment
docker-compose --profile basic up -d
# Run your SSH client tests
pytest tests/ssh_client_tests.py
# Cleanup
docker-compose --profile basic downIdeal for testing web-based SSH clients like WebSSH2:
# Start SSH test server
docker run -d --name webssh-test \
-p 4444:22 \
-e SSH_USER=webuser \
-e SSH_PASSWORD=webpass123 \
-e SSH_DEBUG_LEVEL=2 \
ghcr.io/billchurch/ssh_test:latest
# Configure your WebSSH client to connect to localhost:4444Use as a consistent SSH target for development:
# Development setup with volume mounts
docker run -d --name dev-ssh \
-p 2224:22 \
-e SSH_USER=developer \
-e SSH_PASSWORD=devpass \
-v ./workspace:/home/developer/workspace \
ghcr.io/billchurch/ssh_test:latestContainer fails to start:
# Check container logs
docker logs container-name
# Verify environment variables
docker inspect container-name | jq '.Config.Env'SSH connection refused:
# Test port connectivity
nc -zv localhost 2224
# Check if SSH service is running
docker exec container-name ps aux | grep sshdAuthentication failures:
# Enable debug mode
docker run ... -e SSH_DEBUG_LEVEL=3 ...
# Check authentication configuration
docker exec container-name cat /etc/ssh/sshd_configSSH Public Key Authentication Issues:
When using public key authentication without setting a password (SSH_PASSWORD not provided), the container automatically unlocks the user account to allow key-based authentication. This is necessary because:
- Linux locks user accounts that have no password set (marked with
!in/etc/shadow) - SSH daemon refuses authentication to locked accounts, even with valid SSH keys
- The entrypoint script sets a dummy password hash (
*) which:- Unlocks the account for SSH key authentication
- Still prevents password-based login (no valid password can match
*)
If you experience issues with key authentication:
# Check if user account is locked
docker exec container-name grep username /etc/shadow
# If you see '!' or '!!' in the password field, the account is locked
# Manually unlock (if needed)
docker exec container-name usermod -p '*' username
# Verify SSH key is properly set
docker exec container-name cat /home/username/.ssh/authorized_keysEnable maximum debug output:
docker run -d --name ssh-debug \
-p 2224:22 \
-e SSH_USER=testuser \
-e SSH_PASSWORD=testpass \
-e SSH_DEBUG_LEVEL=3 \
ghcr.io/billchurch/ssh_test:latest
# Watch debug logs in real-time
docker logs -f ssh-debugWhile designed for testing, security best practices are followed:
- Non-root user execution when possible
- Configurable authentication restrictions
- No default passwords in production images
- Regular security updates via automated rebuilds
- Minimal attack surface with optimized Debian slim base
Note: This container is designed for testing environments. Do not expose it directly to the internet without additional security measures.
- Size matters: Ultra-minimal 13.8MB footprint
- Resource-constrained environments: Kubernetes pods, edge computing
- Fast deployment: Quicker download and startup times
- Security: Smaller attack surface with minimal components
- Simple SSH testing: Basic SSH client validation
- Maximum compatibility: Full GNU toolchain and libraries
- Complex applications: Applications requiring specific libraries
- Legacy systems: Compatibility with older SSH clients
- Development: More debugging tools and utilities available
- Production-like testing: Closer to typical server environments
| Metric | Alpine | Debian |
|---|---|---|
| Image Size | 13.8MB | 118MB |
| Download Time | ~2 seconds | ~15 seconds |
| Memory Usage | ~8MB | ~20MB |
| Startup Time | ~1 second | ~2 seconds |
| Compatibility | Good | Excellent |
This project is licensed under the MIT License - see the LICENSE file for details.
See CHANGELOG.md for version history and changes.
- π Issues: Report bugs and feature requests on GitHub Issues
- π Documentation: Comprehensive documentation in this README and code comments
- π¬ Discussions: Community support via GitHub Discussions
- Debian variant: Built with Debian bookworm-slim for optimal size and compatibility (118MB)
- Alpine variant: Built with Alpine Linux for ultra-minimal footprint (13.8MB)
- Uses OpenSSH for robust SSH implementation
- Optimized Docker images with smart OS detection and adaptive configuration
- Dual-architecture support with 88% size reduction possible via Alpine
- Inspired by the need for better SSH integration testing tools