My Little Arrangements with DevContainer - Part 1
DevContainer (devcontainer, Dev Container?) is a specification, based on containers, that describes everything a development environment needs: base image, packages, tools, environment variables, IDE extensions, etc. From this description, a development container is launched and the project is mounted inside it, providing an isolated environment that’s integrated with the IDE (VS Code, Codespaces, JetBrains, etc.).
Microsoft offers “pre-made” images with the usual development tools for popular languages (Go, .NET, Node, Python, Java, etc.). These images are designed to be used directly in a devcontainer.json without having to maintain a custom Dockerfile.
You can add/combine features. Features are reusable blocks that install languages/tools (Node, Docker CLI, Git, etc.)
You can also specify extensions and settings for your IDE (explanations for VSCode)
As far as I’m concerned, I prefer to have full control over my environment and I don’t use pre-made images or features:
- I create my own image, starting from a base image (for example
ubuntu:22.04), and I install all the tools I need. - Eventually I publish this image on Docker Hub to be able to reuse it easily in my projects.
- I also use Docker Compose alongside DevContainer to define additional settings (especially for building my image) and to be able to benefit from multiple services within my DevContainer workspace (like a database for example)
In this first part, I’m going to present my minimal setup for a DevContainer project:
- An Ubuntu-type base image
- A few tools (curl, git, etc.)
- Docker (to be able to launch containers from my DevContainer environment)
Project structure
In the project where I’m going to use DevContainer, I have the following structure:
.devcontainer
├── compose.yml
├── devcontainer.json
└── Dockerfile
Let’s look at the content of each of these files.
Dockerfile
Let’s start with the Dockerfile:
FROM --platform=$BUILDPLATFORM ubuntu:22.04
LABEL maintainer="@k33g_org"
ARG TARGETOS
ARG TARGETARCH
ARG USER_NAME=vscode
ARG DEBIAN_FRONTEND=noninteractive
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US.UTF-8
ENV LC_COLLATE=C
ENV LC_CTYPE=en_US.UTF-8
ARG USER_NAME=vscode: Standard username “vscode” (default value)- Portable image: Can be published on Docker Hub and reused by others
- DevContainer convention: Follows the Microsoft DevContainers standard
DEBIAN_FRONTEND=noninteractive: Avoids interactive prompts during installationLANG/LANGUAGE/LC_*variables: Locale configuration in US English (UTF-8)
# ------------------------------------
# Install Tools
# ------------------------------------
RUN <<EOF
apt-get update
apt-get install -y curl wget jq git build-essential xz-utils software-properties-common sudo sshpass unzip
apt-get clean autoclean
apt-get autoremove --yes
rm -rf /var/lib/{apt,dpkg,cache,log}/
EOF
Installing Basic Tools:
curl,wget: File downloadingjq: JSON parser for command linegit: Version controlbuild-essential: Compilers (gcc, g++, make)xz-utils: Compression/decompressionsoftware-properties-common: Repository managementsudo: Privilege elevationsshpass,unzip: Utilities
# ------------------------------------
# Install Docker
# ------------------------------------
RUN <<EOF
# Add Docker's official GPG key:
apt-get update
apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-model-plugin
EOF
- This section installs a complete Docker stack inside the DevContainer. This section is optional if you don’t need Docker in your development environment.
- Important note: The Docker CLI in the container communicates with the host’s Docker Engine via the
/var/run/docker.socksocket mounted fromdevcontainer.json
# ------------------------------------
# Create a new user
# ------------------------------------
RUN adduser --disabled-password --gecos '' ${USER_NAME}
# Add new user `${USER_NAME}` to sudo group
RUN adduser ${USER_NAME} sudo
# Ensure sudo group users are not asked for a password when using
# sudo command by ammending sudoers file
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN <<EOF
groupadd docker
usermod -aG docker ${USER_NAME}
newgrp docker
EOF
# Add new user `${USER_NAME}` to docker group
RUN adduser ${USER_NAME} docker
# Set the working directory
WORKDIR /home/${USER_NAME}
# Set the user as the owner of the working directory
RUN chown -R ${USER_NAME}:${USER_NAME} /home/${USER_NAME}
# Switch to the regular user
USER ${USER_NAME}
# Avoid the message about sudo
RUN touch ~/.sudo_as_admin_successful
User creation:
--disabled-password: No password required--gecos '': No personal info requested${USER_NAME}: vscode (your local username)
Sudo configuration:
- Adding to sudo group: User can execute admin commands
- NOPASSWD: Sudo without asking for password (convenient in dev)
Docker configuration:
groupadd dockeretc.
- Allows user to execute Docker commands without
sudo
Finalization:
WORKDIR: Sets the working directorychown: Gives home ownership to the userUSER: Switches to non-root user (all following commands execute with this user)touch ~/.sudo_as_admin_successful: Avoids the sudo message on first sudo use
# ------------------------------------
# Install OhMyBash
# ------------------------------------
RUN <<EOF
bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmybash/oh-my-bash/master/tools/install.sh)"
EOF
- Installs Oh My Bash: Framework for customizing the bash shell (themes, plugins, aliases). Totally optional, it’s just to “make it pretty”.
- Executed as non-root user (after
USER ${USER_NAME})
Let’s quickly move on to the compose.yml file.
compose.yml
services:
simple-workspace:
build:
context: .
dockerfile: Dockerfile
network_mode: "host"
volumes:
- ../..:/workspaces:cached
command: sleep infinity
- Service/container name:
simple-workspacecontext: .: Build context = current directory (.devcontainer/)network_mode: "host": Container shares the host’s network.volumes: Mounts the parent directory (2 levels up) in/workspaces. Your source code is accessible in the container. (cached: optimization for macOS)command: sleep infinity: Container stays active indefinitely.
Finally, the devcontainer.json file.
devcontainer.json
{
"name": "Simple Workspace",
"dockerComposeFile": "compose.yml",
"service": "simple-workspace",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
dockerComposeFile: Docker Compose file to useservice: Docker Compose service to useworkspaceFolder: Working directory in the container${localWorkspaceFolderBasename}: Local folder name (this will be your project folder mounted in the container)
"features": {},
"customizations": {
"vscode": {
"extensions": [
"pkief.material-icon-theme",
"pkief.material-product-icons"
]
}
},
"remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
},
//"forwardPorts": [3000],
remoteEnv: defines$LOCAL_WORKSPACE_FOLDERin the container, contains the workspace path on the hostforwardPorts: if uncommented, would forward port 3000 from the container to the host
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind",
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached"
],
"remoteUser": "vscode",
// Forward SSH agent from host (automatic with VSCode)
"forwardAgent": true,
// Run commands after the container is created.
//"postCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} ; sudo chmod 666 /var/run/docker.sock;"
"postCreateCommand": "sudo chmod 666 /var/run/docker.sock;"
}
Mount 1: Docker Socket:
- Mounts the host’s Docker socket in the container
- Enables Docker-in-Docker
Mount 2: Git Configuration:
- Mounts
.gitconfigfrom the host in the container${localEnv:HOME}${localEnv:USERPROFILE}: Works on Linux/macOS (HOME) and Windows (USERPROFILE)- Allows you to use your Git settings
"remoteUser": "vscode":
- Defines the user used in the container
- Uses the standard
vscodeuser (created in theDockerfile)
"forwardAgent": true:
- Enables SSH Agent Forwarding: Automatically forwards your SSH agent from the host to the container (allows you to use your SSH keys without copying them to the container, convenient for Git)
"postCreateCommand":
git config --global --add safe.directory: configures Git to consider the workspace as “safe” (avoids security warnings) ✋✋✋ Errata: you can skip this command.sudo chmod 666 /var/run/docker.sock: Gives necessary permissions for the user to use Docker without sudo (optional, depends on your Docker configuration)
To summarize
┌─────────────────────────────────────────────┐
│ devcontainer.json │
│ (VS Code Configuration + Orchestration) │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Compose │──────│ Extensions │ │
│ │ File │ │ Mounts │ │
│ └──────┬──────┘ └──────────────┘ │
└─────────┼───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ compose.yml │
│ (Docker Compose Orchestration) │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Service: simple-workspace │ │
│ │ - Build context │ │
│ │ - Network mode │ │
│ │ - Volumes │ │
│ └─────────────────┬────────────────────┘ │
└────────────────────┼────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Dockerfile │
│ (Image Building) │
│ │
│ 1. Ubuntu 22.04 │
│ 2. Install tools (git, curl, etc.) │
│ 3. Install Docker │
│ 4. Create user `vscode` │
│ 5. Add `docker` group │
│ 6. Configure `sudo` │
│ 7. Install Oh My Bash │
└─────────────────────────────────────────────┘
That’s it, you can start experimenting with this basic setup. In a second part, I’ll show you how I add additional development stacks (Node, Golang, etc.).
You’ll find the source code for this project on my Codeberg repository: https://codeberg.org/DevContainer-Samples/01-simple