Overview
SSH Portfolio includes a multi-stage Dockerfile that creates a minimal production image using Alpine Linux. This approach keeps the final image small while including all necessary dependencies.
Dockerfile Architecture
The project uses a two-stage build process:
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o ssh-portfolio .
FROM alpine:3.21
WORKDIR /app
COPY --from=builder /app/ssh-portfolio .
COPY config.yaml .
EXPOSE 22
CMD [ "./ssh-portfolio" ]
Stage 1 (Builder): Compiles the Go application using golang:1.24-alpine
Stage 2 (Runtime): Creates a minimal image with only the compiled binary and configuration
Build the Docker Image
Navigate to project directory
Build the image
docker build -t ssh-portfolio:latest .
This creates an image tagged as ssh-portfolio:latest.
Verify the build
docker images | grep ssh-portfolio
Run with Docker
Basic Run Command
docker run -d \
--name ssh-portfolio \
-p 2222:22 \
-v $( pwd ) /config.yaml:/app/config.yaml \
-v ssh-keys:/app/.ssh \
ssh-portfolio:latest
Command Options Explained
-d - Run in detached mode (background)
--name ssh-portfolio - Name the container for easy management
-p 2222:22 - Map host port 2222 to container port 22
-v $(pwd)/config.yaml:/app/config.yaml - Mount your configuration file
-v ssh-keys:/app/.ssh - Persistent volume for SSH host keys
Custom Port Configuration
docker run -d \
--name ssh-portfolio \
-p 3000:3000 \
-e SSH_PORT= 3000 \
-v $( pwd ) /config.yaml:/app/config.yaml \
-v ssh-keys:/app/.ssh \
ssh-portfolio:latest
View Logs
docker logs -f ssh-portfolio
Connect to Your Portfolio
Docker Compose Setup
For easier management and automatic restarts, use Docker Compose:
services :
ssh-portfolio :
build : .
ports :
- "22:22"
environment :
- SSH_PORT=22
volumes :
- ssh-keys:/app/.ssh
restart : unless-stopped
volumes :
ssh-keys :
Using Docker Compose
Start the service
The -d flag runs in detached mode.
Rebuild and restart
docker compose up -d --build
Custom Docker Compose Configuration
For production deployments, you might want to customize the compose file:
services :
ssh-portfolio :
build : .
ports :
- "2222:2222" # Custom port
environment :
- SSH_PORT=2222
volumes :
- ./config.yaml:/app/config.yaml # Mount local config
- ssh-keys:/app/.ssh
restart : unless-stopped
logging :
driver : "json-file"
options :
max-size : "10m"
max-file : "3"
volumes :
ssh-keys :
Persistent SSH Host Keys
SSH host keys must persist across container restarts to avoid “HOST IDENTIFICATION HAS CHANGED” warnings for users.
The ssh-keys volume ensures your SSH host keys remain consistent:
volumes :
- ssh-keys:/app/.ssh
Why this matters:
SSH generates host keys on first run (.ssh/id_ed25519)
Without a volume, keys regenerate on each container restart
Users will see security warnings when keys change
The volume persists keys between deployments
Inspect the Volume
# List volumes
docker volume ls
# Inspect the ssh-keys volume
docker volume inspect ssh-keys
# Backup the volume
docker run --rm -v ssh-keys:/data -v $( pwd ) :/backup alpine tar czf /backup/ssh-keys-backup.tar.gz -C /data .
Container Management
Start
Stop
Restart
Remove
Shell Access
docker start ssh-portfolio
Health Checks
Add a health check to your Dockerfile:
FROM alpine:3.21
WORKDIR /app
COPY --from=builder /app/ssh-portfolio .
COPY config.yaml .
EXPOSE 22
# Add netcat for health checks
RUN apk add --no-cache netcat-openbsd
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD nc -z localhost 22 || exit 1
CMD [ "./ssh-portfolio" ]
Or in docker-compose.yml:
services :
ssh-portfolio :
build : .
ports :
- "22:22"
healthcheck :
test : [ "CMD" , "nc" , "-z" , "localhost" , "22" ]
interval : 30s
timeout : 3s
retries : 3
start_period : 5s
Next Steps
Manual Deployment Deploy without Docker
Environment Variables Configure your deployment