Devcontainer Fragments
Tip: Use @devcontainer
Section titled “Tip: Use @devcontainer”If you are using devcontainers outside of VSCode, use @devcontainer, my simple CLI wrapper around npx @devcontainer/cli that provides better ergonomics.
Some of the features this gives you:
- You don’t need to manually pass
--workspace-folder. - It auto-installs dotfiles.
- It supports custom shells (fish).
- It properly passes TERM environment variables, for proper colors and other behaviors.
- It has rudimentary “stop” and “down” subcommands (a basic feature request since 2023).
Set a container memory limit
Section titled “Set a container memory limit”If you don’t set a memory limit on your containers, they can take down the whole system.
"runArgs": [ "--memory=16G" ]services: app: deploy: resources: limits: memory: 16GHow to write setup scripts
Section titled “How to write setup scripts”When creating a devcontainer, you will often need to run a script to finish setting up the repository. There are two main ways you can go about this.
- Use a lifecycle script. This is the easiest way.
- Use a custom Dockerfile. This is more complex.
Method 1: lifecycle script
Section titled “Method 1: lifecycle script”This is the easiest way to run a few commands after loading a prebuilt devcontainer image. It’s my recommended method for when you are creating a devcontainer that won’t be prebuilt. In this scenario, onCreateCommand can either take a script, or a path to a script in the workspace directory. For example, if you have .devcontainer/devcontainer.json and .devcontainer/setup.sh:
"onCreateCommand": { "local": ".devcontainer/setup.sh"}#!/bin/bashset -eux
sudo apt-get updatesudo apt-get install -y --no-install-recommends libpq-dev postgresql-clientrvm . do gem install bundlerrvm . do bundle installyarn installA quick note about the lifecycle scripts: each lifecycle event runs all of its scripts in parallel. You can take advantage of this by splitting up your script, for example you can yarn install and bundle install in separate scripts:
"onCreateCommand": { "yarn": "yarn install", "bundle": "bundle install",}Method 2: custom Dockerfile
Section titled “Method 2: custom Dockerfile”I don’t recommend bothering with this unless you are planning to prebuild your devcontainer to share with a team. Here’s a basic configuration for a custom Dockerfile, given .devcontainer/devcontainer.json and .devcontainer/Dockerfile.
// Remove the "image" key and replace it with this"build": { "context": "..", "dockerfile": "Dockerfile",}# If you are using Docker Compose, you don't need to change devcontainer.jsonservices: app: build: context: .. dockerfile: .devcontainer/DockerfileFROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04
RUN su vscode -c "gem install rails webdrivers"RUN su vscode -c "/usr/local/rvm/bin/rvm fix-permissions"
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev,.preview.app.github.dev,.app.github.dev"If you don’t want to use a devcontainer image as your base, that’s fine, but you’ll probably want to use the “common-utils” feature instead:
"features": { "ghcr.io/devcontainers/features/common-utils:2": { "installZsh": "true", "username": "vscode", "userUid": "1000", "userGid": "1000", "upgradePackages": "true" }, "ghcr.io/devcontainers/features/git:1": { "version": "latest", "ppa": "false" }},"remoteUser": "vscode",References:
Docker-in-Devcontainer
Section titled “Docker-in-Devcontainer”When you are using a devcontainer that requires other containers (e.g. Postgres, Redis), you have 3 options.
- Use docker-in-docker. Simplest, but provides no security against malicious code.
- Use Docker Compose. Secure, but does not allow building/running new images in the container.
- Use Sysbox. Allows full Docker usage while still being secure, but has a more complicated setup.
Method 1: docker-in-docker
Section titled “Method 1: docker-in-docker”The simplest solution is just to use one of the publicly available docker-in-docker features.
Advantages:
- Easy, configured entirely from
devcontainer.jsonwith no additional dependencies.
Disadvantages:
- Full, unrestricted access to host system. If you want to run a coding agent securely or are running untrusted code, this is not an appropriate solution.
There are two variants of this method:
-
docker-in-docker: all containers, networks, and images will be scoped to the devcontainer, so you can re-use container and network names in different devcontainers without conflicts.
devcontainer.json "features": {"ghcr.io/devcontainers/features/docker-in-docker:2": {}} -
docker-outside-of-docker: just gives you the same docker instance as on the host system, so
docker pswill show all containers running on the entire system.devcontainer.json "features": {"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}}
In general, if you’re going to use one of these methods, use docker-outside-of-docker if you’re not worried about conflicts with container and network names. It will allow you to manage all your docker images in one place and you will be able to re-use downloaded images across container rebuilds. If you are worried about conflicts, then prefer docker-in-docker.
Method 2: Docker Compose
Section titled “Method 2: Docker Compose”If you are creating a devcontainer for a specific project which requires external services, the recommended way to set them up is using Docker Compose.
Advantages:
- Devcontainer CLI / VSCode will take care of starting the containers for you.
- Container images are stored on the host, so you don’t need to re-download them when rebuilding the devcontainer.
- No privileged containers, so it’s safe to
--dangerously-skip-permissions1 and run untrusted code.
Disadvantages:
- Docker is not available in the devcontainer, so you have to manage the other services through the host system.
// Remove the "image" key and replace it with these"dockerComposeFile": "docker-compose.yml","service": "app"services: app: image: mcr.microsoft.com/devcontainers/base:ubuntu-24.04 init: true # Overrides default command so things don't shut down after the process ends. command: sleep infinity volumes: - ..:/workspaces/app:cachedThis is just a normal Docker Compose file, so you can set any properties that you like. This snippet only shows the required keys.
References:
Method 3: Sysbox
Section titled “Method 3: Sysbox”Sysbox is an alternative container runtime (the part of Docker that actually runs containers). It makes your containers behave more like virtual machines, improving isolation and enabling them to run more complex workloads (for example, Docker).
Advantages:
- Full access to Docker inside the container.
- Full isolation from the host system.
Disadvantage:
- Requires installing additional software on the host machine and configuring
devcontainer.json.
To install Sysbox, you can read the fine manual, but the following script works on Ubuntu 24.04 and Sysbox 0.6.7.
# Download and validate the appropriate version from Githubwget https://downloads.nestybox.com/sysbox/releases/v0.6.7/sysbox-ce_0.6.7-0.linux_amd64.debecho 'b7ac389e5a19592cadf16e0ca30e40919516128f6e1b7f99e1cb4ff64554172e sysbox-ce_0.6.7.linux_amd64.deb' | sha256sum -c
# The easiest way to install is to delete all existing containers and# recreate them laterdocker rm $(docker ps -a -q) -f
# Install the packagesudo apt-get install jqsudo apt-get install ./sysbox-ce_0.6.7-0.linux_amd64.deb
# Verify that it's runningsudo systemctl status sysbox -n20Then to set up the container, Sysbox has a variety of different options, but the simplest (just Docker, no systemd) could look like this:
"image": "docker.io/nestybox/ubuntu-noble-docker","features": { "ghcr.io/devcontainers/features/common-utils:2": { "installZsh": "false", "upgradePackages": "false" }},"runArgs": [ "--runtime=sysbox-runc" ],"remoteUser": "ubuntu","postStartCommand": { "dockerd": "sudo -s sh -c 'dockerd -G ubuntu > /var/log/dockerd.log 2>&1 &'",},This configuration will use their Ubuntu 24.04 image with Docker preinstalled, run the container using Sysbox, and start the docker daemon when the container starts.
Footnotes
Section titled “Footnotes”-
With
--dangerously-skip-permissions, and with coding agents in general, any secret stored inside the container can still be exfiltrated using prompt injections, but the rest of your computer is safe. ↩