Luke M

Your next project in a devcontainer

2025-03-13

I recently decided to look into Devpod, which is a command-line tool (and graphical application) that makes it easy to develop within a container for each project.

This means that each project has its own development container, containing dev dependencies and configuration.

A quick overview

Dockerfiles are a set of instructions for building a container.

Docker-compose is a tool (and yaml specification) that allows you to orchestrate multiple containers.

Devcontainers are built from devcontainer.json files, and specifically stand up the development environment.

Benefits of Devcontainers

  • None of the dependencies touch your system
  • If you're using VSCode, it will automatically install extensions that are useful to the project
  • If you are using Neovim, you can pass in a URL for your dotfiles git repository
  • Onboarding other devs is seamless
  • Using them teaches you Docker

A basic setup

Here is a basic setup that creates a dev container with Neovim installed using Homebrew on Debian.

{
  "image": "mcr.microsoft.com/devcontainers/base:debian",
  "features": {
    "ghcr.io/devcontainers-contrib/features/neovim-homebrew": {}
  },
  "remoteEnv": {
    "LANG": "en_US.UTF-8"
  },
  "forwardPorts": [8000]
}

My advice is to create a slimmed down version of your dotfiles, for use with devpods. I shamelessly stole most of mine from Mischa Vandenburg's dotfiles-devpod repository.

devpod up . --dotfiles https://github.com/lkdm/dotfiles-devpod --dotfiles-script setup

Devcontainer Features

Devpod containers with docker-compose

You can stand up other services with your dev container using a docker-compose.yml file. This is useful if what you're developing requires other containers alongside it.

{
  "dockerComposeFile": ["docker-compose.yml"],
  "service": "app",
  "runServices": ["db"],
  "workspaceFolder": "/workspace"
  // ...
}

Using production docker-compose

Notice how the dockerComposeFile property is a list? Devpod uses the first item in the list, and applies YML properties from each subsequent file as a patch.

This means you can have a docker-compose.yml for production in the root of your project, and have a .devcontainer/docker-compose.yml to make changes to certain properties to make it fit for development.

services:
  app:
    environment:
      - DB_SERVER=db
      - DB_PORT=5432
      - DB_NAME=postgres
      - DB_USER=postgres
      - DB_PWD=postgres
      - LOGGING_APP=debug
      - LOGGING_HTTP=info
    ports:
      - "58868"
  db:
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres