Speed up your Scala onboarding with VSCode Devcontainers

Varszegi Kristof
Emarsys Craftlab
Published in
5 min readMar 3, 2023

--

Introduction

If you find the process of setting up your Scala development environment to be laborious and time-consuming, you may want to consider using Visual Studio Code’s devcontainer features. Devcontainers allow you to create a consistent and efficient development environment through the use of Docker, making it possible to streamline your workflow and increase productivity. In this article, I will guide you through the process of utilizing devcontainers in Visual Studio Code for your Scala development projects. The final example code of this article can be located within this gist.

Prerequisites

Creating a devcontainer.json

The first step in utilizing the devcontainer features in Visual Studio Code for your Scala development is to create a “devcontainer.json” file within your project repository, in a directory called .devcontainer. This file will serve as the configuration for your Dockerized development environment, allowing you to add specific dependencies and settings for your project.

Trying with a simple example

As a simple example, let’s create a devcontainer that runs on Ubuntu with no additional dependencies or extensions set up. This devcontainer will serve as a foundation for you to build upon, adding the specific dependencies and extensions required for your project as needed.

{
"name": "Ubuntu",
"image": "mcr.microsoft.com/devcontainers/base:jammy"
}

Using a docker-compose.yml

In a real-world project, it is likely that you will need multiple Docker containers to support your development environment, such as a database. To manage these multiple containers, you can use a docker-compose.yml file in conjunction with the devcontainer.json file. This will allow you to easily spin up and configure all the necessary containers for your project with a single command.

version: "3.8"

services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
command: /bin/sh -c "while sleep 1000; do :; done"
volumes:
- ..:/workspace:cached

By incorporating the additional docker-compose file, you can modify your previous devcontainer.json configuration to the following:

{
"name": "Ubuntu",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace"
}

It is worth noting that the current configuration will not function as intended as the referenced Dockerfile does not exist. However, you will be addressing this by creating the necessary Dockerfile in the next step.

Writing a Dockerfile

To ensure that your development environment has all the necessary tools pre-installed, you can use a Dockerfile in conjunction with the devcontainer.json and docker-compose.yml files. By using a Dockerfile, you can specify all the command-line tools and dependencies that should be installed in the container at the time of creation, saving you the time and effort of manually installing them later on.

The simplest Dockerfile to get started with Scala development in Visual Studio Code looks something like this:

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

At this stage, you can test your devcontainer by selecting the option “Reopen in Container” from the command palette.

Installing sdkman

To further enhance the functionality of your development environment, you can install sdkman using your previously created Dockerfile. Sdkman is a convenient tool for managing multiple versions of Java, as well as other tools and programming languages, such as Scala and SBT. By including sdkman in your Dockerfile, you can easily switch between different versions of Scala and SBT, depending on the requirements of your project.

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

USER vscode

RUN curl -s "https://get.sdkman.io" | bash
RUN chmod +x "$HOME/.sdkman/bin/sdkman-init.sh"
RUN . "$HOME/.sdkman/bin/sdkman-init.sh"

Using a multi-container setup

Your previous docker-compose.yml file only contained one container, named app, which hosted your Visual Studio Code server. To further customize your development environment, you can extend it by adding additional services, such as a PostgreSQL database and a RabbitMQ instance. This allows you to have a complete development environment with all the necessary components, such as a database and message queue, running in separate containers, which can be easily managed and configured through the docker-compose.yml file.

version: "3.8"

services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
command: /bin/sh -c "while sleep 1000; do :; done"
volumes:
- ..:/workspace:cached
rabbit-mq:
image: rabbitmq:3.8.27-alpine
environment:
RABBITMQ_DEFAULT_USER: "rabbit-user"
RABBITMQ_DEFAULT_PASS: "local-rabbit-pass"
postgres:
image: postgres:9.6-alpine
environment:
POSTGRES_PASSWORD: "localpass"

Connecting the services together

To ensure that the different containers in your development environment can communicate with each other, it is important to specify the network_mode in your docker-compose.yml file. This will allow the containers to connect to each other using the specified network mode, allowing for seamless communication between all the components of your development environment.

version: "3.8"

services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
command: /bin/sh -c "while sleep 1000; do :; done"
volumes:
- ..:/workspace:cached
rabbit-mq:
image: rabbitmq:3.8.27-alpine
environment:
RABBITMQ_DEFAULT_USER: "rabbit-user"
RABBITMQ_DEFAULT_PASS: "rabbit-passw0rd"
network_mode: service:app
postgres:
image: postgres:9.6-alpine
environment:
POSTGRES_PASSWORD: "passw0rd"
network_mode: service:app

You can find additional information about network_mode here.

Customizing the development environment

In this section, we will go over the steps of customizing your development environment, including preinstalling tools and themes, to help you achieve a more personalized and effective workflow.

Pre-installing java

You previously installed sdkman as a convenient tool for managing multiple versions of Java and SBT. However, to streamline the development setup process even further, you can avoid the manual work of installing these tools by including them directly in your Dockerfile. This can easily be done by replacing the last line in your Dockerfile with the following:

RUN zsh -c "source $HOME/.sdkman/bin/sdkman-init.sh && sdk install java 11.0.17-zulu"

A similar method could be implemented for sbt and scala, but for the next step, you will be installing Metals which will handle this process for you.

Adding extensions for scala

Visual Studio Code does not have built-in support for the Scala programming language, so it is necessary to install extensions to add that functionality. Two popular extensions for Scala development in Visual Studio Code are the Scala Syntax and Metals. These extensions provide syntax highlighting, code completion and other useful features for Scala development. To make sure that these extensions are automatically installed in your devcontainer, you can save their names in the devcontainer.json file, so they will be automatically installed and configured every time the devcontainer is started.

By clicking on “Add to devcontainer.json”, a new entry will be created in your devcontainer.json file under customizations/vscode/extensions, in the following format:

"customizations": {
"vscode": {
"extensions": [
"scalameta.metals"
]
}
}

By utilizing the specified configuration, you are able to establish a comfortable and intuitive environment for working with Scala, complete with enhanced features such as syntax highlighting, convenient code navigation and a user-friendly test explorer located within the sidebar:

"vscode": {
"extensions": [
"scala-lang.scala",
"scalameta.metals",
"hbenl.vscode-test-explorer",
"Anan.jetbrains-darcula-theme"
],
"settings": {
"workbench.colorTheme": "JetBrains Darcula Theme"
}
}

Trying it all together

To test out your newly created devcontainer development environment, the final step is to clone the repository containing the devcontainer.json, docker-compose.yml and Dockerfile files, open it in Visual Studio Code, and then use the command selector (press Cmd+Shift+P on Mac or Ctrl+Shift+P on Windows/Linux) to select Reopen in Container. This will start the devcontainer, install all the necessary dependencies and extensions, and configure the environment as specified in the configuration files. You are now ready to start developing your Scala project with a streamlined and consistent development environment.

--

--