Creating a Development Environment with DevContainer, Dockerfile and Docker-Compose
Learn how to set up a scalable development environment with DevContainer, evolving from simple configurations to multi-service orchestration with Docker-Compose.
Development environments are often complex configurations of tools, dependency versions, and specific systems. This can make transitioning from one project to another or onboarding new team members difficult. This is where DevContainer comes in, offering a solution that allows you to define and share portable, reproducible development environments.
In this article, I will show you how to get started with DevContainer and how to evolve it based on your needs by going through three stages:
- A basic DevContainer
- A DevContainer with Dockerfile for more fine-tuned customization
- A DevContainer with Docker-Compose to manage multiple services in a development environment
Each step will be introduced by adding a new feature to an example project, showing why and when to evolve the configuration.
In the following examples, the index.html
file is located in the app
folder to illustrate a simple web application.
Step 1: Setting up a Basic DevContainer
The first step is to create a development environment within a container using a very simple configuration. This helps unify environments across a development team without requiring each developer to customize their setup.
Basic Configuration
The simplest configuration for a DevContainer is a devcontainer.json
file, which defines the Docker image to be used for the development container. Here's a minimal devcontainer.json
file to get started:
{
"name": "Basic DevContainer",
"image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu"
}
With this configuration, any developer working on this project can simply open the project in a compatible editor (such as VSCode) and benefit from a ready-to-use Linux environment, without needing to configure anything locally.
Project Structure for a Basic DevContainer:
/my-project
├── .devcontainer
│ └── devcontainer.json
└── app
└── index.html
When to Use This Configuration?
This type of configuration is ideal for simple projects or standard environments where no customization is necessary. If your project only requires a common development environment based on an existing Docker image, this approach is quick and efficient.
Step 2: Adding a Dockerfile for More Control
As development progresses, it often becomes necessary to add specific tools or libraries to the environment. This is where a Dockerfile comes in handy.
Customization with Dockerfile
The second step involves introducing a Dockerfile
that allows you to further customize the environment by adding specific tools or configurations to the project. Here's an example of a devcontainer.json
with an associated Dockerfile
:
{
"name": "Custom DevContainer",
"build": {
"dockerfile": "Dockerfile"
}
}
The Dockerfile
allows you to add instructions to install additional packages:
# Same image as in the minimal devcontainer.json
FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu
# Install additional tools
RUN apt-get update && apt-get install -y curl
Project Structure with Dockerfile Added:
/my-project
├── .devcontainer
│ ├── devcontainer.json
│ └── Dockerfile
└── app
└── index.html
When to Use a Dockerfile?
This configuration is useful when the project has specific needs, such as particular versions of development tools or additional dependencies that the base Docker image doesn’t provide. For example, if you are developing an application that requires certain tools not included in the base image, the Dockerfile allows you to add them.
Step 3: Using Docker-Compose for a Multi-Service Environment
When the project becomes more complex, it may be necessary to orchestrate multiple services, such as a web application and a database. This is where Docker-Compose comes into play.
Managing Multiple Services with Docker-Compose
With Docker-Compose, you can easily orchestrate multiple containers. This allows, for example, running both an API and a database for local development. Here's how to transition to a docker-compose.yml
setup for a multi-service environment:
{
"name": "DevContainer with Docker-Compose",
"dockerComposeFile": "docker-compose.yml",
"service": "app"
}
Le fichier docker-compose.yml
pourrait ressembler à ceci :
services:
app:
build: .
volumes:
- .:/workspace
ports:
- "8080:8080"
db:
image: postgres:latest
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
With this configuration, you can develop and test features that require multiple services in a consistent environment.
Project Structure with Docker-Compose:
/my-project
├── .devcontainer
│ ├── devcontainer.json
│ └── Dockerfile
├── docker-compose.yml
└── app
└── index.html
When to Use Docker-Compose?
Docker-Compose is essential when working with complex systems that require multiple services (such as an API and a database). It allows you to replicate environments similar to production while keeping everything manageable in a local development setting.
Conclusion
As your project's needs evolve, it becomes necessary to adapt your development environment. DevContainer offers great flexibility for this, starting with a simple configuration and evolving into more complex environments using Dockerfile and Docker-Compose.
- Basic DevContainer: Quick and efficient for simple projects.
- DevContainer with Dockerfile: Allows you to customize the tools and configurations in your environment.
- DevContainer with Docker-Compose: Manages multiple services for optimized local development and testing.
I encourage you to try these configurations and adapt them to your specific needs. Using DevContainers can greatly simplify the process of managing development environments, especially in teams where consistency is crucial.