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:

In this first part, I’m going to present my minimal setup for a DevContainer project:

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 installation
  • LANG/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 downloading
  • jq: JSON parser for command line
  • git: Version control
  • build-essential: Compilers (gcc, g++, make)
  • xz-utils: Compression/decompression
  • software-properties-common: Repository management
  • sudo: Privilege elevation
  • sshpass, 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.sock socket mounted from devcontainer.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 docker etc.

  • Allows user to execute Docker commands without sudo

Finalization:

  • WORKDIR: Sets the working directory
  • chown: Gives home ownership to the user
  • USER: 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-workspace
  • context: .: 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 use
  • service: Docker Compose service to use
  • workspaceFolder: 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_FOLDER in the container, contains the workspace path on the host
  • forwardPorts: 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 .gitconfig from 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 vscode user (created in the Dockerfile)

"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

© 2026 k33g Project | Built with Gu10berg

Subscribe: 📡 RSS | ⚛️ Atom