Efficient development using Dockers (Remote Containers)

Discover the power of Docker for efficient project management, especially when utilizing remote containers for seamless collaboration.
Dockers
Python
R
Coding Practices
Author

Pratik Kumar

Published

July 31, 2023

Intro

Dockers are excellent tools in software development. The key benefit of Dockers is that they allow users to package an application with all its dependencies into a standardized unit for software development. They have a low impact on the operating system and are very fast, using minimal disk space. Instead of encapsulating the entire machine, they encapsulate the environments for an application, making it easy to share code, rebuild applications, and distribute them.

Using Microsoft’s extension called : Dev Containers we can harness these benefits. The Dev Containers extension enables the use of a Docker container as a full-featured development environment. This helps developers in the following ways:

  1. Develop with a consistent and reproducible environment.
  2. Quickly swap between different, separate development environments and safely make updates without worrying about impacting your local machine.
  3. Make it easy for new team members / contributors to get up and running in a consistent development environment.

Personally, I have been using Dev Containers to manage projects with different requirements. As a software engineer at Elucidata, I am responsible for developing various applications that require different versions of R, Python, and JavaScript. Remote containers have been immensely helpful in keeping the dedicated packages for each application within their project directories only.

While there are other alternatives such as virtual environments (e.g.- pyenv, venv) and package management tools (for e.g. - packrat, renv), containerization proves to be more efficient for bigger projects where we have to manage these R and Python environments together. With minimal code, we can instruct the required installations and quickly reproduce the environment.

The Dev Containers extension supports two primary operating models:

  1. You can use a container as your full-time development environment
  2. You can attach to a running container to inspect it.

Getting Started

Let’s install the extension and look at an example of how to use this feature.

Requirements

You can use Docker with the Dev Containers extension in a few ways, including:

  • Docker installed locally
  • Docker installed on a remote environment
  • Other Docker compliant CLIs, installed locally or remotely
    • While other CLIs may work, they are not officially supported. Note that attaching to a Kubernetes cluster only requires a properly configured kubectl CLI

Visit here to know more about system requirements.

Installation

To get started, follow this official step by step tutorial here.

Setting up remote container for a project

To use this feature we would need following files and folder :

  1. .devcontainer/devcontainer.json : Required file that is basically a config file that determines how your dev container gets built and started.
  2. .devcontainer/Dockerfile : If we need a custom image as environment.
  3. app/ : The code of the application (can vary based on your project).

.devcontainer/devcontainer.json

Within the .devcontainer folder the devcontainer.json config file helps the extension to determine the name of the container, which image to use, extensions to install, port to expose and other configurations. Few examples,

1. Shiny Application

Consider there are multiple projects that you are working on, and each of them require different R versions and packages. You can set the value of the image key from below to any version of choice for R.

{
    "name" : "Project 1",
    "image": "r-base:latest" //Any version as per requirement from dockerhub
}
{
    "name" : "Project 2",
    "image": "r-base:4.1" //Any version as per requirement from dockerhub
}

2. Using Custom Docker Image

Now, suppose you want to use your own image with a specific R version, package installations, and other tools/languages included. In that case, you can create a custom Dockerfile (just make sure to specify the correct path for this file) as follows,

# Use the specified R base image
FROM r-base:latest

# Set non-interactive mode for apt-get
ENV DEBIAN_FRONTEND=noninteractive

# Expose port 4200
EXPOSE 4200

# Install necessary system dependencies
RUN apt-get update && apt-get install -y \
    make \
    build-essential \
    git \
    wget \
    curl 

## Python setup
# Switch to user "docker" to install pyenv
USER docker
RUN curl https://pyenv.run | bash

# Set up Python paths
ENV PATH="/home/docker/.pyenv/bin:$PATH"
ENV PATH="/home/docker/.pyenv/versions/3.9.13/bin:$PATH"

# Install Python 3.9.13
RUN PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.9.13

# Switch back to root user for the remaining steps
USER root

## Python virtual env and packages
# Copy requirements.txt into the image
COPY requirements.txt requirements.txt

# Create and activate Python virtual environment
RUN rm -rf my_venv && python3 -m venv my_venv && \
    . my_venv/bin/activate && \
    python3 -m pip install --upgrade pip && \
    pip install -r requirements.txt

# Install R packages using renv
COPY renv.lock renv.lock
RUN R -e "install.packages('renv'); renv::restore()"

Now with this Dockerfile, run the extension using following devcontainer.json :

{
    "name": "Custom App",
    "build": {
      "dockerfile": "Dockerfile",  //path to your custom Dockerfile.
      "context": ".."
    },
    "remoteUser": "docker"
}

3. Post Installation Step

When it comes to development, there could be a constant update in list of packages in your project. Now building everytime for each newly added package can be time consuming. Hence, we can install packages after building the image with minimal layers. So that the files would like :

  1. The updated config file .devcontainer/devcontainer.json would now look like:
{
    "name": "Custom App",
    "build": {
      "dockerfile": "Dockerfile", //path to your custom Dockerfile.
      "context": ".."
    },
    "remoteUser": "docker",
    "postCreateCommand": "bash .devcontainer/build_environment.sh" // Can use any scripting language of choice.
}
  1. You can use a bash script to install packages after the build of container .devcontainer/build_environment.sh :
## Install R packages
R -e "install.packages('renv'); renv::restore()"


## Install python packages
rm -rf my_venv && python3 -m venv my_venv && \
    . my_venv/bin/activate && \
    python3 -m pip install --upgrade pip && \
    pip install -r requirements.txt

Thank you!

Thanks for reading! I hope this has helped you understand how to use VSCode’s remote extension, which can boost your productivity.

References and Resources

  1. Docker for beginners
  2. Docker & Kubernetes: The Practical Guide
  3. Remote Containers
  4. Developing inside a container