VSCode devcontainer with zsh, oh-my-zsh and agnoster theme

Jamie Thomson
5 min readMay 8, 2023

--

I’m a fan of Visual Studio Code devcontainers and recently I’ve worked on getting them setup just how I like them with

VSCode terminal with agnoster theme

In this blog post I’ll explain how I’ve done it. In putting this together I’m standing on the shoulders of giants so I do want to thank those people before I get into the details

I’ll assume you already know what devcontainers are and why they’re useful so I won’t get into that here. Everything I’m going to talk about here is shared at

Disclaimer: Those repositories may, and likely will, change after publishing this blog post. For that reason I’ve tagged them at the time of publishing, jamiekt/devcontainer1/tree/2023-05, jamiekt/dotfiles/tree/2023-05.

The devcontainer contains all the cli tools that I like to have available:

cli versions

All are installed using https://containers.dev/features except tree which is baked into the base debian image and jlesswhich isn’t available as a devcontainer feature at the time of writing so is installed via Dockerfile.

The devcontainer also comes configured with my must-have VSCode extensions:

Many of which I mentioned in previous blog post My data engineering dev setup — March 2022.

VSCode extensions installed in devcontainer

My devcontainer.json is available at https://github.com/jamiekt/devcontainer1/blob/2023-05/.devcontainer/devcontainer.json however I’ve provided it below as it existed at the time of publishing this blog post:

{
"name": "devcontainer1",
"build": {
"dockerfile": "./Dockerfile"
},
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/mikaello/devcontainer-features/modern-shell-utils:1": {},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.11"
},
"ghcr.io/devcontainers-contrib/features/pre-commit:2": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind",
"source=devcontainer-bashhistory,target=/commandhistory,type=volume"
],
"customizations": {
"vscode": {
"extensions": [
"mhutchie.git-graph",
"eamodio.gitlens",
"ms-azuretools.vscode-docker",
"github.vscode-github-actions",
"johnpapa.vscode-peacock",
"timonwong.shellcheck",
"robertz.code-snapshot"
],
"settings": {
"editor.tabSize": 4,
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.profiles.linux": {
"bash": {
"path": "bash",
"icon": "terminal-bash"
},
"zsh": {
"path": "zsh"
},
"fish": {
"path": "fish"
},
"tmux": {
"path": "tmux",
"icon": "terminal-tmux"
},
"pwsh": {
"path": "pwsh",
"icon": "terminal-powershell"
}
}
}
}
}
}

Notice that it contains a volume mount which contains my command history.

Here is the accompanying Dockerfile:

FROM mcr.microsoft.com/devcontainers/base:bullseye

ARG USERNAME=vscode

# Used to persist bash history as per https://code.visualstudio.com/remote/advancedcontainers/persist-bash-history
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
&& mkdir /commandhistory \
&& touch /commandhistory/.bash_history \
&& chown -R $USERNAME /commandhistory \
&& echo "$SNIPPET" >> "/home/$USERNAME/.bashrc"

# install jless (because its not available at https://containers.dev/features at time of writing)
RUN NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \
&& (echo; echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"') >> /home/vscode/.profile \
&& eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" \
&& brew install jless

dotfiles

Installing and configuring oh-my-zsh is achieved using VSCode’s support for dotfiles which I only found out about very recently thanks to Christof Marti. I’ve configured the devcontainer extension to use my dotfiles repository https://github.com/jamiekt/dotfiles.

VSCode dotfiles configuration

The install script in that repository, install.sh, is given below:

#!/bin/bash

cat bashrc.additions >> ~/.bashrc

cp ./.gitmessage ~
cp ./.gitconfig ~

# powerline fonts for zsh agnoster theme
git clone https://github.com/powerline/fonts.git
cd fonts
./install.sh
cd .. && rm -rf fonts

# oh-my-zsh & plugins
wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true
zsh -c 'git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions'
zsh -c 'git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting'
cp ./.zshrc ~

########################################################################################################################
#### set agnoster as theme, this came from https://gist.github.com/corentinbettiol/21a6d4e942a0ee58d51acb7996697a88
#### in vscode settings for devcontainer (not for User or Workspace), Search for terminal.integrated.fontFamily value, and set it to "Roboto Mono for Powerline" (or any of those: https://github.com/powerline/fonts#font-families font families).
# save current zshrc
mv ~/.zshrc ~/.zshrc.bak

sudo sh -c "$(wget -O- https://raw.githubusercontent.com/deluan/zsh-in-docker/master/zsh-in-docker.sh)" -- \
-t agnoster

# remove newly created zshrc
rm -f ~/.zshrc
# restore saved zshrc
mv ~/.zshrc.bak ~/.zshrc
# update theme
sed -i '/^ZSH_THEME/c\ZSH_THEME="agnoster"' ~/.zshrc

Here’s what it does:

  • creates some aliases in bash (this existed from before I figured out how to get zsh working). These are a collection of aliases that I always use from the git plugin for oh-my-zsh plus some of my own that I shared at My data engineering dev setup — March 2022.
  • Copies my preferred .gitconfig & .gitmessage
  • Install Powerline fonts, necessary for agnoster theme
  • Install oh-my-zsh with my preferred .zshrc which contains the my aliases
  • Install zsh-autosuggestions
  • Install zsh-syntax-highlighting
  • Configures agnoster

The last thing that needs to be done is configure the terminal to use a Powerline font (I’ve chosen Roboto Mono for Powerline, but I think you can choose any) to make sure that agnoster’s glyphs display correctly. The setting is terminal.integrated.fontFamily :

terminal.integrated.fontFamily setting

Once everything is open in the devcontainer I like to make one final customisation, only show the current directory in the prompt instead of the full path, as explained at https://github.com/agnoster/agnoster-zsh-theme/issues/19#issuecomment-271844106. I do this because otherwise it includes /workspaces/repo-name which I don’t think is useful. You can see the difference in this screenshot:

agnoster prompt showing full path then only current dir

--

--