I already have done a post on how to create a complete Devcontainer using Devpod and Neovim. See reference here.

Recently I was challenged on creating the same setup but for other code editors like VSCode or Intellij. And since I spent some more time on it, I thought it would be also a good idea to explore more complex setups using Devcontainers (like for instance integrating a custom Dockerfile or a custom docker-compose file).

Devcontainers and VSCode/Intellij

Both VSCode and Intellij support Devcontainers through plugins (on Intellij it is even pre-installed). So, the only thing to do is to install the Devcontainer plugin on VSCode if use this text editor and you are good to go.

Setting up the devcontainer

In order to reproduce my setup, I will detail step by step how I did it from scratch and we will complexify the setup as we go.

Step 1: Create a simple devcontainer and test it

In this first step, we will:

  • Create an empty project directory.
  • Create a very simple devcontainer that we can start in VSCode or Intellij.

Execute the following commands to create your project directory:

Bash
mkdir -p test-devcontainer/.devcontainer
touch test-devcontainer/.devcontainer/devcontainer.json

Open the test-devcontainer directory in VSCode (or Intellij) and add the following content to devcontainer.json:

JSON
{
  "name": "test-devcontainer",
  "image": "mcr.microsoft.com/devcontainers/base:debian"
}

You can launch the devcontainer build like so:

  • In VSCode, type CTRL+SHIFT+P > Rebuild and open in dev container (use CMD+SHIFT+P if you use MacOS).
  • In Intellij, right clic on the devcontainer.json file > Dev containers > Create Dev Container and mount sources > Intellij IDEA.

A new container will be built and your IDE window will automatically switch to your new devcontainer.

Step 2: Add a custom Dockerfile

In the Devcontainer spec it is mentionned that you can reference a custom Dockerfile tailored to your needs (in order to install software for example). Lets do that:

In this first step, we will:

  • Create a custom Dockerfile.
  • Modify our devcontainer definition so it is read.

In order to do that, we need to create the Dockerfile:

Bash
touch test-devcontainer/.devcontainer/Dockerfile

And modify our devcontainer.json to read it:

JSON
{
  "name": "test-devcontainer",
  "build": {
    "dockerfile": "Dockerfile"
  }
}

And finally, add the following content to your Dockerfile (feel free to adapt the dependencies to your needs):

YAML
FROM mcr.microsoft.com/devcontainers/base:debian

# add dependencies
RUN apt update && \
  apt install -y software-properties-common apt-transport-https ca-certificates gnupg git tree curl wget bat gpg ripgrep stow cmake tmux fd-find direnv zip unzip

# tell debian to use bash as default shell
RUN ln -snf /bin/bash /bin/sh

Finally, rebuild the container in VSCode or Intellij like we did before. Opening a terminal should allow you to execute any command using the installed software.

Step 3: Add a custom docker-compose

If you want to deploy a full development environment, you will probably need more than just one container (like for instance your app and a database). Here is how to do it:

In this step, we will:

  • Create a docker-compose file with two services.
  • Modify our devcontainer configuration to use it.

in order to do that, we need to create a custom docker-compose file:

Bash
touch test-devcontainer/.devcontainer/docker-compose.yaml

Add the following content to this file:

YAML
services:
  app:
   build:
    context: .
    dockerfile: Dockerfile
   volumes:
     - ..:/workspace/vscode
   depends_on:
     - db

  db:
   image: postgres:latest
   restart: unless-stopped
   ports:
     - 5432:5432
   volumes:
     - postgres-data:/var/lib/postgresql-data
   environment:
    POSTGRES_DB: postgres
    POSTGRES_USER: postgres
    POSTGRES_PASSWORD: postgres

volumes:
  postgres-data:

Very basic, this file creates an app container using your Dockerfile and spins up another container containing a postgresql database.

Next, modify your devcontainer.json file to handle the docker-compose instead of the Dockerfile:

JSON
{
  "name": "test-devcontainer",
  "dockerComposeFile": "docker-compose.yaml",
  "service": "app",
  "workspaceFolder": "/workspace/vscode"
}

The service directive indicates which is the main container your IDE will access (here the app), and the workspace folder in which directory the filesystem will be mounted (usually /workspace/vscode).

Finally, we also need to modify our Dockerfile like so:

YAML
FROM mcr.microsoft.com/devcontainers/base:debian

# add dependencies
RUN apt update && \
  apt install -y software-properties-common apt-transport-https ca-certificates gnupg git tree curl wget bat gpg ripgrep stow cmake tmux fd-find direnv zip unzip

# tell debian to use bash as default shell
RUN ln -snf /bin/bash /bin/sh

# Create the workspace directory
RUN mkdir -p /workspace/vscode
WORKDIR /workspace/vscode

# Keep the container running
CMD ["tail", "-f", "/dev/null"]

We modified this file for the following:

  • Create the workspace directory declared in devcontainer.json.
  • Declare this directory as the default workdir.
  • Make the container loop waiting for instructions (without this last command, the container will start and immediately exit).

Just like before, you can rebuild the devcontainer in VSCode or Intellij. You should see the following:

  • Opening a terminal gives you access to the main service (here app).
  • You can access the second container with the database by doing the following:
Bash
sudo docker ps

This should give you the name of the container running the database. You can then connect to it using the command:

Bash
sudo docker exec -it <container_name> bash

Step 4: (Bonus) execute a custom script

You can even go further in your setup and execute a custom script once the container is created. This could allow you to install third part software for example.

In order to do that, you need to:

  • Create the custome script.
  • Modify your devcontainer.json in order to take it into account.

First, lets create a file for the custom script:

Bash
touch test-devcontainer/.devcontainer/install-software.sh
chmod +x test-devcontainer/.devcontainer/install-software.sh

Add the following content to this new file (for example):

Bash
#!/bin/sh

curl -s https://get.sdkman.io | bash
source $HOME/.sdkman/bin/sdkman-init.sh

sdk install java 24-zulu
sdk install grails 6.2.3

Here, I needed to install sdkman to handle my java and grails installations.

Next, modify the devcontainer.json file as follows:

JSON
{
  "name": "test-devcontainer",
  "dockerComposeFile": "docker-compose.yaml",
  "service": "app",
  "workspaceFolder": "/workspace/vscode",
  "postCreateCommand": "./.devcontainer/install-software.sh"
}

We added a postCreateCommand instruction pointing to our newly created script. When building the container, this script will be executed as last instruction.

Conclusion

In this article, I started from scratch and showed how one can create a complete devcontainer environment using Docker, docker-compose and custom scripts.
This devcontainer can be built using IDEs like VSCode or Intellij which support devcontainers out of the box.


Leave a Reply

Your email address will not be published. Required fields are marked *