Allison Thackston


VSCode, Docker, and Github Pages

As a casual user of Github Pages, I find that I continually have to remember how I set up the development environment so I can edit my website. There’s nothing worse to losing hours to configuration nightmares on coding that is supposed to be for fun. And since I use docker and vscode, it was a pretty easy decision to try to put my website workspace into a container.

Make a docker image

The first thing you need to do is set up an appropriate docker container. This turned out to be a little more difficult than I thought it would be. Since the container needs to support as both a server for the webpage and a development environment, I couldn’t find a vanilla Dockerfile I could use.

At least easily.

But now that I’ve made one, I’m sharing it with you!

Find a base docker image

The first thing you need to do is create a docker image. Docker images always have a FROM tag which lets you build off of other docker images. Sometimes, the best you can do is start with a blank operating system and build your image from scratch. Luckily, jekyll already supports a pages version in their docker repository.

So let’s go ahead and use jekyll’s docker image for pages. By doing this, we can keep up to date with the latest compatible library set for github pages easily.

FROM jekyll/jekyll:pages

This was something that I struggled with when initially creating the image. Recently, there was a change in bundler that was ABI incompatible and the old instructions I was using weren’t working. By using a supported image, the makers of jekyll will be the ones to ensure that all of the packages are compatible and work with github pages.

How cool is that?

Sign me up for getting to the good stuff faster!

Copy setup files

Next, you’ll want to copy the Gemfile from your workspace into the docker container so docker can use it during the build

FROM jekyll/jekyll:pages

COPY Gemfile /srv/jekyll/

My Gemfile is really simple. It only has the github pages gem in it.

source 'https://rubygems.org'
gem 'github-pages', group: :jekyll_plugins

Install dependencies

Even though the base docker image is pages compatible, you can’t develop on it yet. The tools you’ll need to have in order to serve your github pages website locally aren’t included by default. You will need to download and install the development libraries for your Gemfile. But first, you will need to make sure that you have all of the basic libraries you need. Since the image this is based on is a smaller, stripped down image, it may not have many of the libraries you’re used to having by default.

I added the following:

# Install development packages
RUN apk update && apk add \
    # development packages
    ruby-dev \
    gcc \
    make \
    curl \
    build-base \
    libc-dev \
    libffi-dev \
    zlib-dev \
    libxml2-dev \
    libgcrypt-dev \
    libxslt-dev \
    python \
    # pushing to git via ssh
    openssh \
    # permissions to install packages
    sudo \
    # tab completion inside the container
    git-bash-completion

RUN bundle install && bundle update

Note that I’ve added a couple of extra dependencies here as well. I’ve added openssh, sudo, and git-bash-completion.

Update the user

Since we’ll be developing inside a docker container, it’s important that the docker container have a user with your same id and group. This way, when you save files with the container running, you will be saving files with your permissions. You’ll also want that user to have sudo access.

You may have heard that adding a non-root user to your dockerfile is good practice, but giving that user sudo access defeats the purpose. This is true for an image you’re planning on deploying (why lock the door if you’re handing out keys?) but not really necessary for a docker container that’s only going to be used for development like this one.

The user needs sudo access so that you can install extensions and other addons (and is just dang useful to have it).

# Set up user so that we can share ssh credentials.
ENV USERNAME=jekyll
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\
    && chmod 0440 /etc/sudoers.d/$USERNAME

One last thing you may want to do is install completion scripts. Oftentimes when I’m working on code, I’ll want to do things like commit my changes to the github repo from within the integrated terminal. And have nice things like tab-completion working appropriately.

# Set up git completion.
RUN echo "source /usr/share/bash-completion/completions/git" >> /home/$USERNAME/.bashrc

Expose the port

Finally, you’ll want to expose the port that the container will publish to when you server your website locally using bundle exec jekyll serve

EXPOSE 4000

One thing to note here is that the port that this attaches to on your host computer is in the ephemeral port range and not the port that you exposed. You can see what port it’s mapped to on your computer through

$ docker ps
CONTAINER ID        IMAGE                                                     COMMAND                  CREATED             STATUS              PORTS                                 NAMES
3d0128109045        vsc-athackst.github.io-b3b45c0d050bd0f420e78b620fe5b730   "/bin/sh -c 'echo Co…"   24 hours ago        Up 22 seconds       127.0.0.1:4000->4000/tcp, 35729/tcp   laughing_jepsen

The neat thing is, if you click on the website link within the docker terminal window, it will automatically open the page in your browser with the right mapped port!

Put it all together

You should now have a docker file that looks like this:

FROM jekyll/jekyll:pages

COPY Gemfile /srv/jekyll/

WORKDIR /srv/jekyll

RUN apk update && apk add \
    # development packages
    ruby-dev \
    gcc \
    make \
    curl \
    build-base \
    libc-dev \
    libffi-dev \
    zlib-dev \
    libxml2-dev \
    libgcrypt-dev \
    libxslt-dev \
    python \
    # pushing to git via ssh
    openssh \
    # permissions to install packages
    sudo \  
    # tab completion inside the container
    git-bash-completion

RUN bundle install && bundle update

# Set up user so that we can share ssh credentials.
ENV USERNAME=jekyll
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\
    && chmod 0440 /etc/sudoers.d/$USERNAME

# Set up git completion.
RUN echo "source /usr/share/bash-completion/completions/git" >> /home/$USERNAME/.bashrc

EXPOSE 4000

Set up VSCode

In order for VSCode to use your docker file, it will create a devcontainer.json file with all of the docker settings. This includes things like how to launch the docker.

Let’s take a look:

// For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at
// https://github.com/microsoft/vscode-dev-containers/tree/master/containers/ubuntu-18.04-git
{
    "name": "Github Pages",
    "dockerFile": "Dockerfile",
    "context": "../",
    // The optional 'runArgs' property can be used to specify   additional runtime arguments.
    "runArgs": [
        "-u", "jekyll",
        "-v", "${env:HOME}${env:USERPROFILE}/.ssh:/root/.ssh-localhost:ro"
    ],

    // Use 'settings' to set *default* container specific   settings.json values on container create.
    // You can edit these settings after create using File >    Preferences > Settings > Remote.
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash"
    },

    // The port(s) to open from your container.
    "appPort": [4000],

    // Commands to run after the container is created.
    "postCreateCommand": "sudo cp -r /root/.ssh-localhost ~ && sudo chown -R $(id -u):$(id -g) ~/.ssh-localhost && mv ~/.ssh-localhost ~/.ssh && chmod 700 ~/.ssh && chmod 600 ~/.ssh/*",

    // Add the IDs of extensions you want installed when the container is created in the array below.
    "extensions": [
        // yaml for data files
        "redhat.vscode-yaml",
        // liquid templating syntax highlighting
        "sissel.shopify-liquid",
        // jekyll
        "ginfuru.ginfuru-vscode-jekyll-syntax",
        "ginfuru.vscode-jekyll-snippets",
        // markdown
        "yzhang.markdown-all-in-one",
        "davidanson.vscode-markdownlint",
        // html/css
        "ecmel.vscode-html-css",
        "aeschli.vscode-css-formatter",
        // editing
        "streetsidesoftware.code-spell-checker",
        "ms-vscode.wordcount"

    ]
}

Remote user

The first things you’re going to want to do is set the remote user so that your id and group will be properly exported into the container.

    "remoteUser": "jekyll",

App port

Since this container is meant to serve your site locally to debug, you need to be sure to expose the port to your host system.

    // The port(s) to open from your container.
    "appPort": [4000],

You can optionally set it up to run on a specific port by adding the following to the run args.

    "runArgs": [
        "--publish", "4000:4000",
    ]

SSH Credentials

The commands are needed in order for all systems (including windows based systems) to mount their .ssh keys.

    "runArgs": [
    "-v", "${env:HOME}${env:USERPROFILE}/.ssh:/root/.ssh-localhost:ro"
],
    // Commands to run after the container is created.
    "postCreateCommand": "sudo cp -r /root/.ssh-localhost ~ && sudo chown -R $(id -u):$(id -g) ~/.ssh-localhost && mv ~/.ssh-localhost ~/.ssh && chmod 700 ~/.ssh && chmod 600 ~/.ssh/*"

Extensions

The last setting of note is the extensions. These are extensions that will be loaded into the docker container after it has been built. These are not required, but may be nice to include to ensure that productivity boosting extensions are available and configured correctly.

For github pages, I like the following extensions.

    "extensions": [
        // yaml for data files
        "redhat.vscode-yaml",
        // liquid templating syntax highlighting
        "sissel.shopify-liquid",
        // jekyll
        "ginfuru.ginfuru-vscode-jekyll-syntax",
        "ginfuru.vscode-jekyll-snippets",
        // markdown
        "yzhang.markdown-all-in-one",
        "davidanson.vscode-markdownlint",
        // html/css
        "ecmel.vscode-html-css",
        "aeschli.vscode-css-formatter",
        // editing
        "streetsidesoftware.code-spell-checker",
        "ms-vscode.wordcount"
    ]

And that’s it! You’re ready to start your own github pages site.