Docker Setup

Complete guide to setting up Hugo with Docker for your HBStack site. Covers Docker installation, configuration, and containerized development workflows.

Complete guide to setting up Hugo with Docker for your HBStack site. Covers Docker installation, configuration, and containerized development workflows.


Using Docker with Hugo simplifies development by providing a consistent environment across different systems.

Installing Docker

Before setting up your Hugo project with Docker, you need to install Docker and Docker Compose on your system.

Installing Docker on Ubuntu

 1# Update package index
 2sudo apt-get update
 3
 4# Install packages to allow apt to use a repository over HTTPS
 5sudo apt-get install -y \
 6    apt-transport-https \
 7    ca-certificates \
 8    curl \
 9    gnupg \
10    lsb-release
11
12# Add Docker's official GPG key
13sudo mkdir -p /etc/apt/keyrings
14curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
15
16# Set up the Docker repository
17echo \
18  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
19  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
20
21# Install Docker Engine
22sudo apt update
23sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
24
25
26# Add your user to the docker group to run Docker without sudo
27sudo usermod -aG docker $USER
28
29# check if group is present in groups
30groups
31groups $USER
32
33# You should see `docker` present in groups list.
34# If not, you may need to log out and back in for the group change to take effect.
35# Apply the new group membership (or log out and back in)
36newgrp docker
37
38# check if Docker is installed correctly
39docker run hello-world

Installing Docker Compose

Docker Compose comes built-in with recent Docker Desktop installations. For Linux, you might need to install it separately:

1# Download Docker Compose
2sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
3
4# Apply executable permissions
5sudo chmod +x /usr/local/bin/docker-compose
6
7# Create a symbolic link to make it available in your PATH
8sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

Alternatively, you can use the Docker Compose V2 plugin:

1# Docker Compose V2 is now integrated with the Docker CLI as "docker compose"
2docker compose version

Setting Up Hugo with Docker

Creating a docker-compose.yml File

For our Hugo project using the HB Framework, we need to create a docker-compose.yml file that defines our services. Here’s the file structure:

 1name: docker-with-hugo
 2
 3services:
 4  server:
 5    image: hugomods/hugo:exts-non-root
 6    command: server -D --bind 0.0.0.0
 7    volumes:
 8      - ./:/src:rw
 9      - ~/.cache/hugo_cache:/tmp/hugo_cache:rw
10    ports:
11      - 1313:1313
12    env_file:
13      - path: .env
14        required: false
15    restart: unless-stopped
16
17  hugo-mod-update:
18    image: hugomods/hugo:exts-non-root
19    entrypoint: /bin/sh
20    command: >
21      -c "cd /src && echo '📦 Current modules:' && hugo mod graph && echo '\n🔄 Updating modules...' && hugo mod get -u ./... && echo '\n✅ Updated modules:' && hugo mod graph"
22    volumes:
23      - ./:/src:rw
24      - ~/.cache/hugo_cache:/tmp/hugo_cache:rw
25    env_file:
26      - path: .env
27        required: false
28    restart: no
29
30  dev-memory:
31    image: hugomods/hugo:exts-non-root
32    command: server --gc -e development --buildDrafts --enableGitInfo --config config/development/hugo.yaml --renderToMemory -p 1233 --bind 0.0.0.0
33    volumes:
34      - ./:/src:rw
35      - ~/.cache/hugo_cache:/tmp/hugo_cache:rw
36    ports:
37      - 1233:1233
38    env_file:
39      - path: .env
40        required: false
41    restart: unless-stopped
42
43  dev-disk:
44    image: hugomods/hugo:exts-non-root
45    command: server --gc -e development --buildDrafts --logLevel=info --enableGitInfo --config config/development/hugo.yaml -p 1433 --bind 0.0.0.0
46    volumes:
47      - ./:/src:rw
48      - ~/.cache/hugo_cache:/tmp/hugo_cache:rw
49    ports:
50      - 1433:1433
51    env_file:
52      - path: .env
53        required: false
54    restart: unless-stopped
55
56  validate:
57    image: hugomods/hugo:exts-non-root
58    command: hugo --gc --panicOnWarning --renderToMemory --logLevel=info
59    volumes:
60      - ./:/src:rw
61      - ~/.cache/hugo_cache:/tmp/hugo_cache:rw
62    env_file:
63      - path: .env
64        required: false

Understanding the Docker Compose Configuration

Let’s break down the key components of this configuration:

  1. Project Name

    1name: docker-with-hugo
    

    Defines the project name, which is used in container naming.

  2. Service Definitions We define three services for different development scenarios:

    • server: Standard development server
    • dev-memory: Development server with rendering to memory
    • dev-disk: Development server with rendering to disk
  3. Docker Image

    1image: hugomods/hugo:exts-non-root
    

    Uses the official HugoMods Docker image that includes:

    • Extended version of Hugo with SCSS support
    • Node.js and NPM for PostCSS processing
    • Git for Hugo modules
    • All necessary dependencies for HB Framework
  4. Command Each service has a specific command:

    1command: server -D --bind 0.0.0.0 # For the default server
    

    This launches Hugo’s development server with drafts enabled and binds to all network interfaces.

    1# For dev-memory
    2command: server --gc -e development --buildDrafts --enableGitInfo --config config/development/hugo.yaml --renderToMemory -p 1233 --bind 0.0.0.0
    

    The parameters mean:

    • --gc: Run garbage collection during the build
    • -e development: Use development environment
    • --buildDrafts: Include draft content
    • --enableGitInfo: Get last Git revision for each page
    • --config config/development/hugo.yaml: Use specific config file
    • --renderToMemory: Store rendered pages in memory (faster performance)
    • -p 1233: Use port 1233
    • --bind 0.0.0.0: Bind to all network interfaces
  5. Volumes

    1volumes:
    2  - ./:/src:rw
    3  - ~/.cache/hugo_cache:/tmp/hugo_cache:rw
    

    Maps your local project directory to /src inside the container with read-write permissions, and uses a persistent cache to speed up builds.

  6. Ports

    1ports:
    2  - 1313:1313 # For default server
    3  - 1233:1233 # For development servers
    

    Maps container ports to host ports so you can access your Hugo site in a browser.

  7. Environment Variables

    1env_file:
    2  - path: .env
    3    required: false
    

    Allows for loading environment variables from a .env file.

  8. Restart Policy

    1restart: unless-stopped
    

    Ensures the container restarts automatically unless explicitly stopped, providing better reliability.

Running Hugo with Docker

Starting the Development Server

You can start any of the predefined servers using Docker Compose:

1# Start the default server (accessible at http://localhost:1313)
2docker compose up server
3
4# Start the memory-based development server (accessible at http://localhost:1233)
5docker compose up dev-memory
6
7# Start the disk-based development server (accessible at http://localhost:1433)
8docker compose up dev-disk

Add the -d flag to run in detached mode:

1docker compose up -d server

Stopping the Server

To stop and remove the containers:

1docker compose down

Building for Production

To generate a production-ready site:

1docker compose run --rm server hugo --minify

This command:

  • Runs the hugo --minify command in the server service
  • The --rm flag removes the container after it completes
  • The --minify flag compresses HTML, CSS, JS, and XML files

Managing Hugo Modules

Hugo Modules provide a way to share and reuse components across Hugo sites. To update all modules:

1docker compose run hugo-mod-update

To add a new module, edit config/_default/module.yaml and then run the update command.

Removing any orphans

To remove any orphaned containers that are not defined in your docker-compose.yml file:

1docker compose <up or down> --remove-orphans

Removing all stopped containers

To remove all stopped containers, you can use the following command:

1docker container prune

This command will prompt you for confirmation before removing all stopped containers. If you want to skip the confirmation, you can add the -f flag.

Docker Container Management Best Practices

When working with Docker for Hugo development, you might accumulate many stopped containers over time. If you found 10 stopped containers when running docker container prune, it’s a sign that you should establish a regular maintenance routine. Here are best practices for managing your Docker containers:

Cleaning Frequency

Here’s a recommended cleaning schedule based on usage patterns:

  1. Daily Active Development: If you’re actively developing every day:

    • Run docker container prune at the end of each workday
    • Use the --rm flag with docker run commands to automatically remove containers after they exit
  2. Occasional Development: If you work on your Hugo site a few times a week:

    • Run docker container prune once a week
    • Consider setting up a weekly scheduled task
  3. Project-Based Cleanup: If you switch between multiple projects:

    • Run docker container prune when switching between projects
    • Use project-specific container naming to easily identify containers

Container Management Workflow

Here’s an effective workflow to minimize container clutter:

  1. Start your day with:

    1# Check what containers exist before you begin
    2docker ps -a
    3
    4# Remove any dangling containers from previous work
    5docker container prune -f
    
  2. During development:

    1# Always use the --rm flag for one-off commands
    2docker run --rm -v "$(pwd)":/src -w /src hugomods/hugo:exts-non-root hugo version
    3
    4# Use named containers for longer-running processes
    5docker compose up -d --name hugo-dev server
    
  3. End your day with:

    1# Stop all running containers for this project
    2docker compose down
    3
    4# Clean up any leftover containers
    5docker container prune -f
    

Additional Management Commands

Here are some useful commands for container maintenance:

 1# List all containers (running and stopped)
 2docker ps -a
 3
 4# See how much disk space Docker is using
 5docker system df
 6
 7# Get detailed information about a specific container
 8docker inspect <container_id>
 9
10# Remove containers with a specific status (like 'exited')
11docker rm $(docker ps -q -f status=exited)
12
13# Stop and remove all containers
14docker stop $(docker ps -a -q) && docker rm $(docker ps -a -q)
15
16# Complete system pruning (containers, images, networks, and volumes)
17docker system prune -a

Comprehensive Docker Management

Monitoring Docker Resources

To effectively manage your Docker environment, it’s important to regularly monitor resource usage:

 1# Check overall Docker disk usage
 2docker system df
 3
 4# Get detailed breakdown of space usage
 5docker system df -v
 6
 7# Monitor real-time resource usage of running containers
 8docker stats
 9
10# Check logs for a specific container
11docker logs <container_id>
12
13# Follow logs in real-time
14docker logs -f <container_id>

Advanced Cleanup Commands

For more thorough cleanup beyond just containers:

 1# Remove unused images (not just dangling ones)
 2docker image prune -a
 3
 4# Remove all unused volumes
 5docker volume prune
 6
 7# Remove all unused networks
 8docker network prune
 9
10# Complete system cleanup (containers, networks, images, and build cache)
11docker system prune -a --volumes

Automating Docker Maintenance

1. Create a Comprehensive Cleanup Script

Create a file named docker-maintenance.sh in your project root:

 1#!/bin/bash
 2
 3# Comprehensive Docker maintenance script
 4
 5echo "📊 Current Docker status:"
 6echo "========================="
 7docker ps
 8echo ""
 9echo "Disk usage before cleanup:"
10docker system df
11echo ""
12
13echo "🛑 Stopping all running containers..."
14docker stop $(docker ps -q) 2>/dev/null || echo "No running containers to stop."
15echo ""
16
17echo "🧹 Cleaning up Docker resources..."
18echo "Removing all stopped containers..."
19docker container prune -f
20echo ""
21
22echo "Removing unused images..."
23docker image prune -a -f
24echo ""
25
26echo "Removing unused volumes..."
27docker volume prune -f
28echo ""
29
30echo "Removing unused networks..."
31docker network prune -f
32echo ""
33
34echo "✅ Cleanup complete!"
35echo ""
36echo "Disk usage after cleanup:"
37docker system df
38echo ""
39
40echo "🚀 Starting essential services..."
41cd /path/to/your/hugo/project
42docker compose up -d server
43echo "Done! Your Hugo server should be running at http://localhost:1313"

Make it executable:

1chmod +x docker-maintenance.sh

2. Schedule Regular Maintenance with Cron

To automate cleanup, add a cron job:

1crontab -e

Add one of these lines depending on your preferred schedule:

1# Run daily at midnight
20 0 * * * /path/to/your/project/docker-maintenance.sh >> /path/to/your/project/logs/docker-maintenance.log 2>&1
3
4# Run weekly on Sunday at midnight
50 0 * * 0 /path/to/your/project/docker-maintenance.sh >> /path/to/your/project/logs/docker-maintenance.log 2>&1
6
7# Run at system startup
8@reboot sleep 60 && /path/to/your/project/docker-maintenance.sh >> /path/to/your/project/logs/docker-maintenance.log 2>&1

3. Add Maintenance Commands to package.json

If you’re using npm/yarn in your project, add these scripts to your package.json:

1"scripts": {
2  "dev": "docker compose up server",
3  "dev:memory": "docker compose up dev-memory",
4  "dev:disk": "docker compose up dev-disk",
5  "build": "docker compose run --rm server hugo --minify",
6  "docker:status": "docker ps && echo '\\nResource usage:' && docker stats --no-stream",
7  "docker:cleanup": "docker system prune -a -f --volumes",
8  "docker:restart": "docker compose down && docker compose up -d server"
9}

Then you can run them with:

1npm run docker:status
2npm run docker:cleanup

4. Add a Maintenance Service to docker-compose.yml

Add this service to your docker-compose.yml:

 1services:
 2  # ... your existing services ...
 3
 4  maintenance:
 5    image: docker:cli
 6    volumes:
 7      - /var/run/docker.sock:/var/run/docker.sock
 8    command: >
 9      sh -c "
10        echo 'Running Docker maintenance...' &&
11        docker system prune -a -f --volumes &&
12        echo 'Maintenance complete!'
13      "
14    profiles:
15      - maintenance

Run it with:

1docker compose --profile maintenance up maintenance

Docker Resource Limits

To prevent Docker containers from consuming too many resources, add limits to your services in docker-compose.yml:

 1services:
 2  server:
 3    # ... existing configuration ...
 4    deploy:
 5      resources:
 6        limits:
 7          cpus: '0.5'
 8          memory: 512M
 9        reservations:
10          cpus: '0.25'
11          memory: 256M

Docker Health Checks

Add health checks to ensure your services are running properly:

1services:
2  server:
3    # ... existing configuration ...
4    healthcheck:
5      test: ['CMD', 'curl', '-f', 'http://localhost:1313']
6      interval: 1m
7      timeout: 10s
8      retries: 3
9      start_period: 30s

Docker Networking Best Practices

For better isolation and security:

1services:
2  server:
3    # ... existing configuration ...
4    networks:
5      - hugo_network
6
7networks:
8  hugo_network:
9    driver: bridge

Watchtower for Automated Container Updates

For keeping your Docker images up-to-date:

1docker run -d \
2  --name watchtower \
3  -v /var/run/docker.sock:/var/run/docker.sock \
4  container/watchtower \
5  --interval 86400 \
6  --cleanup

This will check for updates once a day and clean up old images.


Environment Configuration

You can customize your Hugo environment by creating a .env file in your project root. This file is optional but useful for development.

Example .env file:

 1# Hugo environment: development or production
 2HUGO_ENVIRONMENT=development
 3
 4# For direct module downloads without proxy
 5HUGO_MODULE_PROXY=direct
 6
 7# Set cache directory (already configured in docker-compose.yml)
 8# HUGO_CACHEDIR=/tmp/hugo_cache
 9
10# Enable verbose output for debugging
11# HUGO_VERBOSE=true
12
13# Custom parameters for your theme
14# HUGO_PARAMS_THEME_VARIANT=dark

To see all environment variables available:

1docker compose run server env

The Docker Image

Image used in this setup is hugomods/hugo:exts-non-root, which includes:

  • Hugo Extended (with SCSS/SASS support)
  • Git for version control
  • Go for Hugo Modules
  • Node.js for JavaScript processing
  • Running as non-root user for security

Learn more about this image at docker.hugomods.com


Why Use Docker with Hugo?

Using Docker with Hugo offers several advantages:

  1. Consistent Environment: Everyone working on the project gets the exact same Hugo version, dependencies, and configuration.

  2. No Local Dependencies: You don’t need to install Hugo, Go, Node.js, PostCSS, or any other dependencies directly on your system.

  3. Version Control: You can specify exact versions of Hugo to use, preventing compatibility issues when Hugo updates.

  4. Cross-platform: Works the same way across different operating systems (Linux, macOS, Windows).

  5. Isolation: Prevents conflicts with other software on your system.

  6. Easy Setup: New team members can start contributing quickly without complex setup.

Automating Docker Commands with VS Code Tasks

For more convenient operation, you can use VS Code tasks to run Docker commands. Create a .vscode/tasks.json file with the following content:

 1{
 2  "version": "2.0.0",
 3  "tasks": [
 4    {
 5      "label": "Hugo: Start Server (Default)",
 6      "type": "shell",
 7      "command": "docker compose up server",
 8      "problemMatcher": [],
 9      "group": {
10        "kind": "build",
11        "isDefault": true
12      }
13    },
14    {
15      "label": "Hugo: Start Dev Server (Memory)",
16      "type": "shell",
17      "command": "docker compose up dev-memory",
18      "problemMatcher": []
19    },
20    {
21      "label": "Hugo: Start Dev Server (Disk)",
22      "type": "shell",
23      "command": "docker compose up dev-disk",
24      "problemMatcher": []
25    },
26    {
27      "label": "Hugo: Stop All Containers",
28      "type": "shell",
29      "command": "docker compose down",
30      "problemMatcher": []
31    },
32    {
33      "label": "Hugo: Build Production Site",
34      "type": "shell",
35      "command": "docker compose run --rm server hugo --minify",
36      "problemMatcher": []
37    },
38    {
39      "label": "Hugo: Update Modules",
40      "type": "shell",
41      "command": "docker compose run hugo-mod-update",
42      "problemMatcher": []
43    },
44    {
45      "label": "Docker: Show Status",
46      "type": "shell",
47      "command": "docker ps && echo '\\nResource usage:' && docker stats --no-stream",
48      "problemMatcher": []
49    },
50    {
51      "label": "Docker: Clean Up Resources",
52      "type": "shell",
53      "command": "docker system prune -a -f --volumes",
54      "problemMatcher": []
55    }
56  ]
57}

You can then run these tasks from VS Code by pressing Ctrl+Shift+P and typing “Tasks: Run Task”.


FAQ