Skip to main content

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

1

Navigate to project directory

cd ssh-portfolio
2

Build the image

docker build -t ssh-portfolio:latest .
This creates an image tagged as ssh-portfolio:latest.
3

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

ssh localhost -p 2222

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

1

Start the service

docker compose up -d
The -d flag runs in detached mode.
2

View logs

docker compose logs -f
3

Stop the service

docker compose down
4

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

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

Build docs developers (and LLMs) love