I use Docker to run several microservices across my websites. Generally, they are standalone tools and demonstrations, mostly made to teach myself how to get these things done in microservices.

I recently came across the concept of utility containers. Up to now, all the containers I run are meant to run all the time, and be easily replaced by spinning up a replacement container. Utility containers, on the other hand, are literally containers that hold the bits needed to process input data into output data, do that process, then exit.

At the same time, I’ve been transitioning from Google’s blogger platform onto my in-home hardware, using Hugo. I’ve written my own little Docker utility container to hold the go language and the Hugo executable, specifically made to process data checked out from my hugo-content and hugo-static (private) repositories and automatically check the outputs back into my blog-htdocs repository.

I’m writing this post directly on my gitlab website, and it will be the first post that I’m writing directly into source control, to be processed by my hugo-builder container, and posted publicly.

Detail: Dockerfile

# Docker 20.10.16
FROM		alpine:latest
MAINTAINER	Gary Allen Vollink g.hugo@vollink.com

RUN apk update \
    && apk upgrade \
    && apk add coreutils shadow bash openssh curl go git \
    && mkdir /root/.ssh \
    && ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 \
        -N '' -C 'git@hugobuilder' -q \
    && chmod 700 /root/.ssh \
    && chmod 600 /root/.ssh/id_ed25519 \
    && chmod 644 /root/.ssh/id_ed25519.pub \
    && curl -LOs https://github.com/gohugoio/hugo/releases/download/v0.99.1/hugo_0.99.1_Linux-64bit.tar.gz \
    && cd /usr/local/bin \
    && tar xfz /hugo_0.99.1_Linux-64bit.tar.gz \
    && rm LICENSE README.md \
    && /bin/echo "#######################" \
    && /bin/echo "## Add key to gitlab." \
    && /bin/echo "#######################" \
    && cat /root/.ssh/id_ed25519.pub \
    && /bin/echo "#######################"

COPY src/* /run/
CMD /run/entry.sh

Detail: entry.sh

For now, I’m not sharing this script. The entry.sh script is 249 lines long. Here are the key points:

  • If /work exist (if so, it came from -v on the command line):

    • Store /work owner’s UID WORK_UID
    • Store /work group’s GID WORK_GID
  • If /work does NOT exist:

    • WORK_UID=33, WORK_GID=33 (Ubuntu’s www-data u/gids)
    • Create /work, set ownership to WORK_UID:WORK_GID
  • If alpine:latest does not having a group with WORK_GID

    • Create a group: groupadd --gid $WORK_GID hugobuilder
  • If alpine:latest does not having a user with WORK_UID

    • Create a user, hugobuilder, with WORK_GID and WORK_UID
  • If alpine:latest did have a matching user or group:

    • Matching user is WORK_NAME or hugobuilder
    • Matching group is GROUP_NAME or hugobuilder
  • Modify WORK_NAME:

    • Add GROUP_NAME to WORK_NAME account
    • If WORK_NAME has no home directory, add/create (not /work).
      • This is WORK_NAME_HOME
    • Set WORK_NAME shell to /bin/bash
  • Modify WORK_NAME_HOME:

    • If there is somehow an .ssh or .gitconfig already there:
      • Back up any .ssh and .gitconfig that are “in the way”
    • If a /work/.ssh folder exists, copy it to WORK_NAME_HOME
    • Else, copy the /root/.ssh to the user folder.
  • Check for /work/config.yaml or /work/config.toml:

    • Set this in the environment for the next script.
  • Execute the go_hugo.sh (script in next section, below):

  • Cleanup/revert any .ssh changes

  • Cleanup/revert any .gitconfig changes

Details go_hugo.sh

#!/bin/bash
#############################################################################
VAR_ERROR=""
cd /work
# Read the environment package that entry.sh left us.
if [ -r "$1" ]
then
    echo "Reading $1"
    eval $(cat "$1")
else
    echo "Unable to read $1"
    ls -ld "$1"
fi
# Read any environment package that a user put in /work
if [ -r "/work/hugobuilder.env" ]
then
    eval $(cat "/work/hugobuilder.env")
fi
# Check for expected variables
#   These should all have something, even if left unused.
if [ -z "$GROUP_NAME" ]
then
    VAR_ERROR="${VAR_ERROR}GROUP_NAME:"
fi

# SKIPPING THE REST OF THE CHECKS FOR BREVITY

if [ ! -z "$VAR_ERROR" ]
then
    echo "ERR: Expected variables missing: ${VAR_ERROR}"
    echo "HAS_CONFIG=$HAS_CONFIG"
    exit 2
fi

if [ -z "$GIT_SSH_COMMAND" ]
then
    # ONLY if the user has not given us a better one.
    GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null"
    GIT_SSH_COMMAND="${GIT_SSH_COMMAND} -o StrictHostKeyChecking=no"
fi
export GIT_SSH_COMMAND

if [ -r "/work/.gitconfig" ]
then
    # If it exists, this was already backed up by entry.sh
    cp "/work/.gitconfig" "${WORK_HOME}/.gitconfig"
fi

# See if we have the two settings needed for `git commit`
# Assume we don't
_NEED_GCFG_E=1
_NEED_GCFG_N=1
if [ -r "${WORK_HOME}/.gitconfig" ]
then
    grep 'user.email' "${WORK_HOME}/.gitconfig" 2>&1 >/dev/null 
    if [ "0" = "$?" ]
    then
        # Unless we find it
        _NEED_GCFG_E=0
    fi
    grep 'user.name' "${WORK_HOME}/.gitconfig" 2>&1 >/dev/null 
    if [ "0" = "$?" ]
    then
        _NEED_GCFG_N=0
    fi
fi

# Add needed git settings.
if [ "1" = "${_NEED_GCFG_E}" ]
then
    git config --global user.email "hugobuilder-auto@vollink.com"
fi
if [ "1" = "${_NEED_GCFG_N}" ]
then
    git config --global user.name "Hugo Builder Automation"
fi

if [ ! -d "/work/blog/.git" ]
then
    git clone --recursive \
        ssh://git@gitlab.home.vollink.com:30022/external/blog-htdocs.git \
        "/work/blog"
    if [ "0" -ne "$?" ]
    then
        echo "ERROR: git failed."
        echo "Was key added to gitlab?"
        echo "===>"
        cat ${WORK_HOME}/.ssh/id_ed25519.pub
        echo "<==="
        exit 1
    fi
    if [ ! -d "/work/blog/htdocs" ]
    then
        echo "ERROR: git claims success, but blog/htdocs was not created."
        exit 1
    fi
fi
cd /work
if [ ! -d "/work/hugo-blog/.git" ]
then
    git clone --recursive \
        ssh://git@gitlab.home.vollink.com:30022/home/web/hugo-blog.git \
        "/work/hugo-blog"
    if [ "0" -ne "$?" ]
    then
        echo "ERROR: git failed."
        echo "Was key added to gitlab?"
        echo "===>"
        cat ${WORK_HOME}/.ssh/id_ed25519.pub
        echo "<==="
        exit 1
    fi
    if [ ! -d "/work/hugo-blog" ]
    then
        echo "ERROR: git claims success, but hugo-blog/ was not created."
        exit 1
    fi
fi

cd /work/hugo-blog
git fetch --all
git pull
git submodule foreach git pull origin master

##
# before running hugo: do I have a config?
if [ -r "${HAS_CONFIG}" ]
then
    cp "${HAS_CONFIG}" "/work/hugo-blog/."
fi
/usr/local/bin/hugo --destination "/work/blog/htdocs"
# This is bullshit, by the way... if there is a git FOLDER in the
# destination, hugo will delete it entirely before replacing everything,
# so I've set this up so the destination is one layer deep.
rm /work/blog/htdocs/.git
cd /work/blog
git add .
if [ "0" = "$?" ]
then
    git commit -m 'docker hugobuilder automated check-in.'
    if [ "0" = "$?" ]
    then
        git push origin master
        if [ "0" -ne "$?" ]
        then
            exit 1
        fi
    fi
fi

Update

The page above was run through the hugobuilder container and deployed using a git pull from my web server. With this edit, I’m going to attempt to let the hugobuilder and my various crontabs deploy this automatically (checks are done on a 10 minute schedule).

Update 2

This page is updating from source check-ins alone, so I’m feeling really good. A few things above were updated since the last update, but a lot less has changed than I initially expected.