I wanted to learn a bit more about using devcontainers and GitHub CodeSpaces, and thought the code/content repo for this site would be a good place to start.
Development Containers
The Development Containers spec was originally started by Microsoft to provided a simple experience to edit codes in predictable containerised development environments from Visual Studio Code via the Visual Studio Code Dev Containers extension.
Amongst the benefits are:
- predictable development environment
- easy to setup new developers
- easy way to cross-target, for example edit a .Net application on a windows machine (with Docker and WSL2) that will target a Linux environment.
When running on a Windows host, the tooling takes advantage of WSL2 and Docker for Windows to run the environment in Linux-based containers. Windows-based containers are not supported.
However many web development tools work best in a Linux environment, so this potentially allows the author to stay in their familiar Windows filesystem whilst taking advantage of the Linux services in WSL2.
I started with the community-contributed Hugo devcontainer config, and made the following changes:
- change the Hugo variant to
hugo_extended
- needed by the SASS elements in the site third-party theme - change the Node version to
18
- added a feature to include Go in the build - needed because the theme is included via a module
- extended the VS Code extensions list I wanted
The final version is shown at the end of this post.
I tried two configurations locally, running on a Windows 11 laptop with WSL2 and Docker for Windows configured:
From Windows filesystem
In this scenario the VS Code IDE is running in Windows connecting to a remote container in WSL2.
TL;DR - At first it seemed that everything started correctly, but when I tried to edit the files in the container only the top-level of the source directory was visible. The file name of files in sub-directories were visible but the file content could not be accessed.
A further problem showed itself when I updated the devcontainer.json
file to automatically run npm install
, this failed at the point of trying to create the node_modules
folder.
Given these symptoms my working assumption is that this is to do with a mismatch between windows permissions model / user ids and the container, but I haven’t had time to investigate further.
From WSL2 filesystem
I normally edit this repo in Ubuntu on WSL2 using the VS Code WSL extension that allows an IDE running in Windows to edit files in WSL2. (This is completely invisible to the user, you just type code .
in your WSL directory at the bash prompt.)
There is still a use case for development containers WSL2 - WSL2 as this allows the development environment to be predictable for that project, regardless of what changes you may have made to your WSL2 setup.
In simple terms, this “just worked”.
Github Codespaces
Github codespaces are a feature which provides Github users with a cloud-based on-demand development environment for their repositories. It supports remote development from a local IDE but for many the key benefit is the provision of a web-based version of Visual Studio Code. This allows remote development using only a browser.
There is plenty of documentation on how to set this up, and it all worked as it says, or at least until I tried to run a development build of the site with hugo serve -D
, which I found would randomly crash.
I had a vague feeling this might be to do with memory (I had heard that hugo serve
could be memory-hungry) so re-ran the command with the --printMemoryUsage
flag. Sure enough it peaked just below the maximum memory of the VM (default codespace VM is 2 cores / 8GB RAM) before it fell over. To increase the RAM available you need to jump up to 8 cores / 16GB RAM, and at this configuration the command worked, with a maximum allocation on this site of 8.6GB.
Although this size of machine does eat up the compute allotment, even on the free tier you get 120/8 = 15 hours per month. I already pay for a Github Pro subscription, so within that $4 price you get 180/8 = 22.5 hours per month. For the few times I need to edit the blog in a browser this seems a very reasonable compromise.
Update
By setting the container user to root
the process will work from a windows directory too.
Final version of the configuration
This gist shows the Development Container configuration for running a Hugo blog |
{ | |
"name": "Hugo (Community)", | |
"build": { | |
"dockerfile": "Dockerfile", | |
"args": { | |
// Update VARIANT to pick hugo variant. | |
// Example variants: hugo, hugo_extended | |
// Rebuild the container if it already exists to update. | |
"VARIANT": "hugo_extended", | |
// Update VERSION to pick a specific hugo version. | |
// Example versions: latest, 0.73.0, 0,71.1 | |
// Rebuild the container if it already exists to update. | |
"VERSION": "latest", | |
// Update NODE_VERSION to pick the Node.js version: 12, 14 | |
"NODE_VERSION": "18" | |
} | |
}, | |
"features": { | |
"ghcr.io/devcontainers/features/go:1": { | |
"version": "1.20.1" | |
} | |
}, | |
// Configure tool-specific properties. | |
"customizations": { | |
// Configure properties specific to VS Code. | |
"vscode": { | |
// Set *default* container specific settings.json values on container create. | |
"settings": { | |
"html.format.templating": true, | |
"license-header-manager.excludeExtensions": [ | |
".sh", | |
".xml", | |
".html" | |
], | |
"license-header-manager.excludeFolders": [ | |
"node_modules", | |
"plugins", | |
"assets" | |
], | |
"editor.tabCompletion": "on", | |
"eslint.alwaysShowStatus": true, | |
"eslint.lintTask.enable": true, | |
"eslint.useESLintClass": true, | |
"jest.jestCommandLine": "npm run test --" | |
}, | |
// Add the IDs of extensions you want installed when the container is created. | |
"extensions": [ | |
"bierner.markdown-footnotes", | |
"budparr.language-hugo-vscode", | |
"bungcip.better-toml", | |
"davidanson.vscode-markdownlint", | |
"dbaeumer.vscode-eslint", | |
"eamodio.gitlens", | |
"GitHub.vscode-pull-request-github", | |
"hediet.vscode-drawio", | |
"mushan.vscode-paste-image", | |
"sdras.night-owl", | |
"stevensona.character-count", | |
"streetsidesoftware.code-spell-checker", | |
"Tyriar.lorem-ipsum", | |
"yzhang.markdown-all-in-one" | |
] | |
} | |
}, | |
// Use 'forwardPorts' to make a list of ports inside the container available locally. | |
"forwardPorts": [ | |
1313 | |
], | |
// Use `updateContentCommand` to run commands in the prebuild | |
//"updateContentCommand": "npm install", | |
// Use 'postCreateCommand' to run commands after the container is created. | |
// "postCreateCommand": "uname -a", | |
"postCreateCommand": "npm install", | |
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. | |
"remoteUser": "node" | |
} |
# Update the NODE_VERSION arg in docker-compose.yml to pick a Node version: 18, 16, 14 | |
ARG NODE_VERSION=16 | |
FROM mcr.microsoft.com/devcontainers/javascript-node:${NODE_VERSION} | |
# VARIANT can be either 'hugo' for the standard version or 'hugo_extended' for the extended version. | |
ARG VARIANT=hugo_extended | |
# VERSION can be either 'latest' or a specific version number | |
ARG VERSION=latest | |
# Download Hugo | |
RUN apt-get update && apt-get install -y ca-certificates openssl git curl && \ | |
rm -rf /var/lib/apt/lists/* && \ | |
case ${VERSION} in \ | |
latest) \ | |
export VERSION=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | grep "tag_name" | awk '{print substr( |
|
esac && \ | |
echo ${VERSION} && \ | |
case $(uname -m) in \ | |
aarch64) \ | |
export ARCH=ARM64 ;; \ | |
*) \ | |
export ARCH=64bit ;; \ | |
esac && \ | |
echo ${ARCH} && \ | |
wget -O |
|
tar xf ${VERSION}.tar.gz && \ | |
mv hugo /usr/bin/hugo | |
# Hugo dev server port | |
EXPOSE 1313 | |
# [Optional] Uncomment this section to install additional OS packages you may want. | |
# | |
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ | |
# && apt-get -y install --no-install-recommends <your-package-list-here> | |
# [Optional] Uncomment if you want to install more global node packages | |
# RUN sudo -u node npm install -g <your-package-list-here> | |