Creating a Go Project in Docker with MS Visual Studio Code
Coding for the Internet in 2023 means Microservice architecture, which means containers and container orchestration. To assist with the best practices, Adam Wiggins from Heroku published his team’s developers guidelines, called The Twelve Factor App. According to Wikipedia, Nginx extended the principles, and O’Reilly added their own two cents. However, I consider the original principles a solid beginning.The Twelve Factor App is found at https://12factor.net/.
Using this guide, one will create a Go project that immediately follows the principles of “II. Explicitly declare and isolate dependencies.”
This isn’t hard, really, since Go
- is a compiled language that creates statically-linked binaries by default
- is reverse-compatible with previous minor versions.
However, Go pulls its dependencies into a common area unless one switches their GOPATH. I prefer keeping my systems clean, so Docker it is.
A. Ingredients
- Ensure Docker or Docker Desktop is running.
- Download and install Microsoft Visual Studio Code on Mac, Linux, or PC.
- Install the following extensions:
- Docker
- Remote Development. This pack includes a bunch tools, including the absolutely vital Dev Containers.
- Rewrap (optional: keeps your comments easy to read)
- Markdownlint (optional: keeps your documentation clean)
B. Project Setup
The developer will want to set this section up as a template so it can be used over and over again for new projects.
B.1. Directory structure
The Go project layout at https://github.com/golang-standards/project-layout is overkill for beginning projects. Until the developer knows which modules he or she will create, there is no point in adding too many directories and files. The baseline should be:
<project name>
|- .devcontainer/
| |- Dockerfile
| |- devcontainer.json
| |- config/
| | |- requirements.txt # for mkdocs or other Python tools.
|- cmd/ # subdirectories for each CLI command being built.
|- docs/
|- pkg/ # non-main packages
|- .gitignore
|- Makefile
|- README.md
If using mkdocs, add mkdocs.yaml and VERSION.cfg to the root as well.
B.2 The Python requirements
The Python requirements file config/requirements.txt is for setting up mkdocs. If you are not using mkdocs, just keep the Python requirements file empty. If you do not need Python at all, get rid of the requirements.txt file and the Python references in the Dockerfile below.
mkdocs
mkdocs-autorefs
mkdocs-mermaid2-plugin
mkdocs-material
mkdocs-material-extensions
mkdocstrings
B.3 Dockerfile
Here are the contents of the Dockerfile. This installs a decent terminal, Python for documentation tools, Go Visual Studio tools, and a series of useful Go binaries and linters.
ARG GO_VERSION=1.20
ARG ALPINE_VERSION=3.17
FROM golang:$GO_VERSION-alpine${ALPINE_VERSION}
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=1000
ARG GOPLS_VERSION=latest
RUN adduser $USERNAME -s /bin/sh -D -u $USER_UID $USER_GID && \
mkdir -p /etc/sudoers.d && \
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME && \
chmod 0440 /etc/sudoers.d/$USERNAME
RUN apk add -q --update --progress --no-cache \
git sudo openssh-client zsh curl zsh-vcs make gpg graphviz \
python3 yamllint jq curl unzip git
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools
RUN go install golang.org/x/tools/gopls@${GOPLS_VERSION}
RUN for tool in tools/gopls tools/cmd/goimports lint/golint; \
do go install golang.org/x/${tool}@latest; \
done
# Detect shadowing bugs
RUN go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
# Visual Studio Code tools
RUN go install github.com/cweill/gotests/gotests@latest
RUN go install github.com/fatih/gomodifytags@latest
RUN go install github.com/josharian/impl@latest
RUN go install github.com/haya14busa/goplay/cmd/goplay@latest
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN go install honnef.co/go/tools/cmd/staticcheck@latest
# Setup shell
USER $USERNAME
RUN sh -c "$(wget -O- https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" "" --unattended &> /dev/null
ENV ENV="/home/$USERNAME/.ashrc" \
ZSH=/home/$USERNAME/.oh-my-zsh \
EDITOR=vi \
LANG=en_US.UTF-8 \
PATH=/home/vscode/.local/bin:$PATH
RUN printf 'ZSH_THEME="agnoster"\nENABLE_CORRECTION="false"\nplugins=(git copyfile extract colorize dotenv encode64 golang)\nsource $ZSH/oh-my-zsh.sh' > "/home/$USERNAME/.zshrc"
RUN echo "exec `which zsh`" > "/home/$USERNAME/.ashrc"
# To run mkdocs server as per Eitri standards
COPY config/* .
RUN python3 -m pip install -r requirements.txt
USER root
# Allow the developer to download third-party modules
RUN chown -R $USER_UID:$USER_GID /go/pkg
Notes
- There must be a Go image available with both the Alpine and Go versions the developer wants. Some Alpine/Go combinations have had trouble, so one should do their research. The default listed in the first two ARG commands I know personally to work. Rather than change the default here, change the devcontainer.json file.
- Zsh and the Oh-my-zsh are my personal preferences. By all means, throw them out if you do not want them. Since the build stage is separate from the release and run stages, it does not matter.
- Likewise, you may not want Python tools in your build environment. Since I use mkdocs, I keep the Python and its config/requirements.txt.
B.4 The Devcontainer configuration
Once the developer has created the devcontainer.json file, then the environment is ready to be built and executed.
- Note the ALPINE_VERSION and GO_VERSION fields. These will override the defaults in the Dockerfile.
- Note also the Git SSH key mappings. Change this to reflect your own settings.
- Lastly, notive the vscode.vim local extension. If you are not a vi or vim user, get rid of it.
{
"name": "Automation 2.0 NA",
"build": {
"dockerfile": "Dockerfile",
"args": {
"ALPINE_VERSION": "3.17",
"GO_VERSION": "1.20"
}
},
"forwardPorts": [8000],
"remoteUser": "vscode",
"customizations": {
"vscode": {
"extensions": [
"davidanson.vscode-markdownlint",
"golang.Go",
"ms-azuretools.vscode-docker",
"ms-vscode.go",
"ms-vscode.makefile-tools",
"vscodevim.vim"
]
},
"settings": {
"go.useLanguageServer": true
}
},
"runArgs": [
"-u",
"vscode",
"--cap-add=SYS_PTRACE",
"--security-opt",
"seccomp=unconfined",
// map SSH keys for Git
"-v", "${env:HOME}/.ssh:/home/vscode/.ssh:ro"
]
}
B.5. Gitignore
It is a bad thing to check in binaries, coverage reports and secrets. The gitignore here will ensure that Git does not push certain files or directories up.
curl https://raw.githubusercontent.com/github/gitignore/main/Go.gitignore \
> .gitignore
echo '# Binaries' >> .gitignore
echo bin/ >> .gitignore
C. Use the Environment
C.1 Copy the Template
Using cp -a <template> <project_name>
, create a copy of the template
directory you set up in section B.
C.1 Build Container and Test
Either open VS Code and open the new directory (CTRL-K, CTRL-O) or cd into the
new directory and execute code .
VS Code will ask to re-open the folder in a
container, to which you naturally respond ‘Yes.’
After a few minutes (you can look at the logs for a progress report), the container will be ready. To test, open the Terminal tab in VS Code then "+" > zsh. To make absolutely sure, type:
go version
C.2 Create Module
Since Go works, start work!
go mod init <project url>
This creates a go.mod file, which in turns permits a properly functioning
go run
and go build
system.