Personal Dev Environment using Docker and Neovim


On Mac, I ran into an issue with bits/stdc++.h not being natively available. To fix it, I’d have to use awkward workarounds like copying or symlinking the file - it got the job done, but wasn’t ideal.

I’ve noticed many inconsistencies when developing on an environment that isn’t consistent. To work around this, I started using VSCode’s Remote Docker extension, which lets me access and edit files in any Docker container from within the editor. It’s really convenient! As someone who prefers Neovim for daily use, I thought it would be awesome to bring a similar experience to my go-to editor.

How to achieve this?

Technically, we can execute any shell program inside Docker container, even neovim

Step 1: Select Appropriate Image for use

First, pick an image for your dev container - popular options are standard Linux choices like Ubuntu or Alpine. We’ll stick with Ubuntu here since it includes everything needed for a competitive coding setup.

After we have selected the image, let’s create a Dockerfile in preferred directory of your choice. Update the contents of Dockerfile with following content.

# Dockerfile
FROM ubuntu:18.04

Step 2: Install necessary dependencies

At this point, the container only contains the image. For competitive coding, we require to install some other necessary dependencies like compiler (clang), LSP for sweet auto-completion and neovim for editing.

Append the following commands in the Dockerfile.

#  Dockerfile

RUN apt-get update && apt-get install -y \
  xz-utils \
  build-essential \
  curl \
  && rm -rf /var/lib/apt/lists/* \
  && curl -SL https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz \
  | tar -xJC . && \
  mv clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04 clang_10.0.0 && \
  echo 'export PATH=/clang_10.0.0/bin:$PATH' >> ~/.bashrc && \
  echo 'export LD_LIBRARY_PATH=/clang_10.0.0/lib:$LD_LIBRARY_PATH' >> ~/.bashrc

# Install neovim and other necessary deps for use
RUN apt-get update && apt-get install -y python3.6 git locales neovim python3-neovim

At this stage, your Dockerfile should see something like this.

FROM ubuntu:18.04

# Make sure the image is updated, install some prerequisites,
# Download the latest version of Clang (official binary) for Ubuntu
# Extract the archive and add Clang to the PATH
RUN apt-get update && apt-get install -y \
  xz-utils \
  build-essential \
  curl \
  && rm -rf /var/lib/apt/lists/* \
  && curl -SL https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz \
  | tar -xJC . && \
  mv clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04 clang_10.0.0 && \
  echo 'export PATH=/clang_10.0.0/bin:$PATH' >> ~/.bashrc && \
  echo 'export LD_LIBRARY_PATH=/clang_10.0.0/lib:$LD_LIBRARY_PATH' >> ~/.bashrc

RUN apt-get update && apt-get install -y python3.6 git locales

Now you can build the Dockerfile using the following command. You can execute this in terminal of your choice.

# Make sure to be in the same directory as the `Dockerfile`.
# It will build the image from Dockerfile.
docker build -t competitive .

Step 3: Mount the directories

Your environment is ready for usage, only thing remaining now is to mount the file system to container so that we can share the output file generated inside the container to the host machine.

To perform this, we can use volumes to attach directories from the host system to the Docker container.

This can be done via the following command, which will run the container from competitive image.

docker run \
    -e TERM=screen-256color \
    -v $(pwd):/app/competitive \
    -v $HOME/.dotfiles/nvim/.config/nvim:/root/.config/nvim \
    --name competitive -dit competitive

Above command will run the image competitive in a container name competitive, and attach the directories from the host operating system to the Docker container.

Step 4: Ready

Now that the setup is almost done, you can exec into the container and start working on the files. You will get the exact libs for work which you require from the comfort of the different host operating system.

docker exec -it competitive bash

Gotcha

When you restart the system, Docker containers will be stopped. You need to explicitly start the container using container id next time.

To solve this repetitive action, I created a small script to find the container with the name competitive and start the container if ti is not already present.

#!/bin/sh

containerId=`docker ps -a -f 'name=competitive' --format "{{.ID}}"`
if [ ! -z "$containerId" ]; then
    echo "Found existing container, Starting it"
    docker start $containerId
else
    echo "Container not found with name competitive, creating one"
    docker run \
        -e TERM=screen-256color \
        -v $(pwd):/app/competitive \
        -v $HOME/.dotfiles/nvim/.config/nvim:/root/.config/nvim \
        --name competitive -dit competitive
fi

echo "Starting container"
docker exec -it competitive bash

In the above snippet, we are first checking if there is any container with the name competitive. If there is, then it fetches the container ID and starts it, else it creates a new container and exec into it.

I had a great time exploring docker and what we can achieve via some simple scripts.