Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Welcome to devconcurrent, your new development environment manager!

Organization

This document is largely divided into two sections; User Guide for how to setup your machine and personal configuration and Project Guide for how to configure a project.

In both sections, we try to dive into how devconcurrent works a bit, so it may be valuable for you to skim both, even if you’re only setting up one of the two.

Value Proposition

With git worktrees, you can work on multiple branches of a project in different directories at the same time.

With devcontainers, you can have isolated development environments, but there isn’t great tooling outside of VS Code, and the reality of ports make this harder than it should be.

Enter devconcurrent, a tool to easily manage worktrees + devcontainers; it can bring them up, take them down, exec into them, etc all with simple CLI commands.

With dc up foo you have a brand new worktree named foo, running its devcontainer, ready for you! Once you’re done, just dc destroy foo.

On top of that, devconcurrent gives you a local DNS server and TLS-terminating proxy. With a little bit of setup, if you have some web app app and are working on feature3, you can view it in your browser at https://feature3.app.test!

Installation

Prerequisites

The only real requirement of devconcurrent is that you have a project in a git repository.

If you want to use all of the features of devconcurrent, you’ll need a modern docker, and be interested in setting up a devcontainer.json file with docker-compose.

You’ll also need to spend a small amount of effort to integrate devconcurrent’s DNS server and CA with your system to be able to access your container by URL.

Binary Installation

Devconcurrent ships standard GitHub releases, is available as a brew tap, and has a nix flake.

brew install paholg/tap/devconcurrent

See also GitHub Releases, where there are release binaries and an install script.

Shell Setup

Once installed, it’s highly recommended you place the following in your shell config file (.bashrc or similar).

source <(COMPLETE=bash devconcurrent)

The rest of this document assumes you have run this, but if you have not, replace dc commands with devconcurrent, and you’ll lose some of the functionality.

You are welcome to run just COMPLETE=$SHELL devconcurrent to see what it’s doing, but it’s these three things:

  1. It generates completions; we try to have very good shell completions for you.
  2. It generates the dc function. In addition to letting you type less, this lets devconcurrent change your directory when you ask it to; it’s what powers dc go and similar commands.
  3. It generates a shell hook if you have shell.exportEnv = true. This powers having environment variables automatically set for you whenever you’re in a workspace.

Configuration

Devconcurrent requires a small config file, where you must at least list the projects with which you wish to use it.

The config is a toml file at devconcurrent/config.toml in your platform’s standard configuration directory.

Until you create this file, any devconcurrent command will return an error with the expected path; if you are unsure where to put the file, that’s the easiest way to see.

At a minimum, it must list a project with its location:

[projects.foo]
path = "~/src/foo"

The order here matters; the first project will be considered your “default” project, and will be used when no other project can be inferred.

Note: devconcurrent has a published config schema. I recommend a tool like tombi, which will highlight errors and give you auto-completion.

Most project configuration lives in the project’s devcontainer.json, in customizations.devconcurrent, but we also let you specify devcontainer settings here, letting you specify per-user overrides, or even specifying a full devcontainer configuration for a project that does not have one.

Example:

[projects.foo]
path = "~/src/foo"
devcontainer = {
  mounts = [{
    type = "bind",
    source = "/my/special/mount",
    target = "its/too/special"
  }]
}

See the full Configuration Reference for the full set of options.

Introduction

Like an ogre, devconcurrent has layers. Unlike an ogre, you are free to keep only those that you wish.

The next sections discuss how to set up and use each layer. Use as many or as few as you like.

Workspaces

A workspace is an instance of a project; essentially a git worktree plus an optional devcontainer. It should be a self-contained development environment, at least up to shared caches and the like.

We refer to the original workspace as the root workspace; its name is always the same as the project name, and no other workspaces can be created with that name.

I recommend that you do no work in the root workspace. New workspaces branch from the root. You can keep them clean by keeping the root always on the main branch.

I also recommend that workspaces other than the root are short-lived. For me, one workspace is one pull request, and then I destroy it. This helps keep me organized; I always have a 1-1 relationship between workspaces, branches, and pull requests.

Commands

All of devconcurrent’s commands pick a project in the following priority:

  1. A top-level -p/--project flag.
  2. The DEVCONCURRENT_PROJECT environment variable.
  3. Your current working directory.
  4. Your first configured project.

This way, if you have several projects, you can define aliases like alias dcc="dc -p cookit" to always target that project, or you can just cd into the project you want to work on first.

Here are some commands for workspaces; see the CLI Reference or dc --help for the full commands and options.

dc up NAME

Create a new workspace. Use -g/--go to also go to it.

When a workspace is created, it checks out a branch named by the worktree.branch template in your config.toml, which defaults to the workspace name. If that branch does not exist, it is cut from the root project. Use -b/--branch to use a different branch name or -d/--detach for no branch.

To prefix your branches, set the template globally or per project:

[worktree]
branch = "plg/{{ workspace }}"

[projects.my-app]
path = "~/src/my-app"
branch = "{{ project }}/{{ workspace }}"

With this, dc up foo creates worktree foo on branch plg/foo, or my-app/foo in my-app. Templates get project and workspace; see the config.toml reference.

dc go NAME

Go to a workspace. Because devconcurrent’s completions are so nice, I alias this to just d, so I can do e.g. d f<TAB> to go to the floopydoopy workspace.

dc destroy NAME

Destroy a workspace. The names is optional; if blank, targets the current workspace, and moves you to the root after completion.

dc status

Show all of your workspaces in a table, with git status information. The symbols here are modeled after starship.

dc show

Use dc show workspace to see the current workspace, or exit 1 if not in a workspace. This can be used for prompt-integration and the like.

Devcontainers

A devcontainer is essentially a docker compose environment with some extra tooling built around it.

It gives you some services you can configure, and a primary service, which is where you are intended to work. However, devconcurrent understands that you may not always want to work in the container, and aims to make your life easy and painless whether you’re inside or outside of it.

Currently, we do not support the full devcontainer spec; we only support compose-based containers, and we don’t support features. If you need either of these, please open an issue.

Configuration

This is covered primarily in Porject Devcontainer Setup, but I want to point out here that you don’t need the project to explicitly support devconcurrent, or even devcontainers at all. You can put any devcontainer.json overrids in your config.toml.

Commands

With devcontainers, some of the commands mentioned in Workspaces are enchriched, and others are added.

dc up NAME

When creating a workspace, we launch the devcontainer, running all lifecycle commands. You can use this on an existing workspace to recreate it. If I would like my container updated with upstream changes, I will sometimes run

git rebase origin/main && dc up

dc destroy NAME

This will always remove the containers and any volumes.

dc status

This command is enriched with information about the containers; status, memory used, etc.

Use -l/--live to include CPU and keep it running, and -w/--workspace NAME to instead view a single workspace by container.

dc show

There are many more options here; run dc show --help to see them all. These all tend to just output short strings for use in prompts and the like.

Port Forwarding

NOTE: If you’re planning on continuing to the DNS section, you can skip this.

In devcontainer.json, there’s a section for forwardPorts; this is what we use for forwarding ports. However, we do not pay attention to portsAttributes; forwarding ports with devconcurrent is an explicit action, so the semantics are a bit different than with a long-lived editor.

When ports are forwarded, services in containers see requests from the host as coming from localhost. This is accomplished by creating two thin docker sidecars; one inside the target’s network namespace, and one that listens to the port on the host.

Managing ports is cumbersome and error-prone; it’s easy to forget which workspace has the ports at any moment. Instead, in the next sections, we’ll cover accessing the containers by hostname so you never have to do this.

Commands

dc fwd

Start forwarding ports for the current workspace (or one given by `-w/–workspace).

If any ports are already forwarded by devconcurrent, they are moved to this workspace. If any are already in use by something else, we log a warning and forward what we can.

This allows you to “move” ports to the current workspace with a simple dc f.

dc fwd stop

Stop forwarding ports.

dc show ports

Show what ports are being forwarded to the current workspace; this can be useful in a shell prompt if you’d like a reminder if you’re currently forwarding here.

DNS

This is where the true magic starts to happen, allowing you to access each container by a unique hostname.

By default, when its proxy is enabled, devconcurrent runs a DNS server on port 43770 that listens for the TLD .test. You can configure either of these, but I strongly recommend you limit devconcurrent to TLDs that cannot serve real traffic.

There are several reserved TLDs, and you can read about them on Wikipedia.

In order for this to work, you need to point your system to use devconcurrent for DNS for just .test.

See the platform-specific instructions for how to do this.

To check the health of all stages involved in the proxy, run dc proxy status. This should help point out any gaps in your setup.

System Setup

In order for devconcurrent to be able to resolve your containers’ hostnames, your system needs to defer to it for the .test TLD. Because we’re just using it for a reserved TLD that cannot serve real traffic, it should not affect the rest of your DNS queries.

See the instructions for your operating system, then head to Verification

DNS: Linux

On Linux, there are various different methods your system may be handling DNS; we cover the most common ones here.

The instructions below aim to not just tell you how to set up the DNS, but to help find out which applies to you.

systemd-resolvd

Run systemctl is-active systemd-resolvd. If that reports active:

sudo mkdir -p /etc/systemd/resolved.conf.d
printf '[Resolve]\nDNS=127.0.0.1:43770\nDomains=~test\n' \
  | sudo tee /etc/systemd/resolved.conf.d/devconcurrent.conf
sudo systemctl restart systemd-resolved

NetworkManager

NOTE: These instructions should work, but I do not have a system that uses NetworkManager for DNS. If you use them, please report back!

Run systemctl is-active NetworkManager. If that reports active:

NetworkManager only does conditional DNS forwarding when it’s using its dnsmasq backend, which is not the default. Check whether it’s enabled:

NetworkManager --print-config | grep -A3 '\[main\]'

If dns isn’t dnsmasq, enable it with a drop-in:

printf '[main]\ndns=dnsmasq\n' | sudo tee /etc/NetworkManager/conf.d/dns.conf

Then tell dnsmasq to forward .test to devconcurrent:

printf 'server=/test/127.0.0.1#43770\n' \
  | sudo tee /etc/NetworkManager/dnsmasq.d/test.conf

Then restart NetworkManager:

sudo systemctl restart NetworkManager

NixOs

On NixOs, you can configure resolved declaratively:

services.resolved = {
  enable = true;
  settings.Resolve = {
    DNS = "127.0.0.1:43770";
    Domains = "~test";
  };
};

Other

If none of this works for you, please file an issue.

DNS: macOS

Docker Desktop does not provide container IPs to the host, which we need to be able to direct traffic. Here are two tools that can do this:

  • Docker Mac Net Connect is an open source tool for doing just this using wireguard.
  • OrbStack is a proprietary alternative to Docker Desktop that offers this via “Direct container access”.

Once that’s resolved, you’ll need to configure your system to have devconcurrent handle DNS for .test:

sudo mkdir -p /etc/resolver && \
  printf 'nameserver 127.0.0.1\nport 43770\n' | \
  sudo tee /etc/resolver/test

DNS: Windows Setup

Unfortunately, we don’t yet have a solution for Windows. Like Mac, container IPs are not reachable from the Windows host. Unlike Mac, there are no existing tools to help with this.

You should be able to do everything in WSL2 using the Linux instructions, but you won’t be able to resolve your container hostnames from the Windows host.

Stay tuned, we’ll try to have a fix for this soon!

DNS: Verification

For some project with at least one devcontainer service and the proxy enabled:

{
  ...,
  "customizations": {
    "devconcurrent": {
      "proxy": {
        "enable": true,
        }
      }
    }
  }
}

Run dc up. This will start the workspace containers as well as the DNS server.

Run dc proxy status; you should see green checks for DNS and RESOLV for your service(s). If DNS is red, then there’s something wrong with devconcurrent, and if RESOLV is red, then devconcurrent is not getting traffic from your OS.

If your services are listening on any ports, you should be able to reach them on those ports using the hostname; for an http service at setup.app.test that listens on port 3000, curl setup.app.test:3000 should work.

HTTP

If you’ve gotten this far, you’re in pretty good shape; you have isolated workspaces, and you can access each one by a unique hostname.

There are really only two issues that remain:

  1. You still have to remember what ports things listen on!
  2. TLS

Ports

Fixing 1 is trivial for http servers; just set httpProxyPort to the port a service listens on:

{
  ...,
  "customizations": {
    "devconcurrent": {
      "proxy": {
        "enable": true,
        "services": {
          "app": {
            "httpProxyPort": 8080
          },
          "jaeger": {
            "httpProxyPort": 16686
          }
        }
      }
    }
  }
}

Then, on dc up, devconcurrent will automatically launch an HTTP proxy on port 80 for those services, so you can access them by hostname, no port.

TLS

What I mean by “TLS”, is that as soon as you direct a browser to something other than localhost, it starts to get grumpy if its not a secure connection. Similarly, many web frameworks will get upset if they serve traffic on what they deem to be non-local hostnames without https.

The good news is that we can fix that; devconcurrent can generate a CA and we just need to tell your system or browser to trust it.

This sounds scary, but devconcurrent’s CA is only valid for its configured TLDs, so as long as those don’t include TLDs that can serve real traffic (you didn’t put com in there or something, right?), there’s not much to worry about.

Run dc proxy trust to do just that.

If this does not work for you, or if you’d prefer to trust the CA yourself, you can find it at dc show ca-root. This points to a directory containing the file rootCA.pem. You can import that into your browser’s certificates via its UI, or add it on NixOs with:

security.pki.certificateFiles = [ ./path/to/rootCA.pem ]

Once that’s done, everything should just work. The proxy also serves traffic on port 443, and if you run dc proxy status while running a service in every container with a configured httpProxyPort, you should see only green.

Introduction

[Under Construction]

Devcontainer Setup

[Under Construction]

CLI Reference

Here is the full CLI reference. You can also see any of this by passing --help to devconcurrent or any of its subcommands.

CLI

This document contains the help content for the devconcurrent command-line program.

Command Overview:

devconcurrent

A tool for managing devcontainers, especially when combined with git worktrees

Usage: devconcurrent [OPTIONS] <COMMAND>

Subcommands:
  • up — Bring up a workspace, creating it if it does not exist
  • exec — Exec into a running devcontainer
  • fwd — Forward configured forwardPorts to a running workspace
  • compose — Run docker compose against the given workspace
  • destroy — Fully destroy the workspace; equivalent to docker compose down -v --rmi local --remove-orphans && git worktree remove
  • show — Show some value
  • status — Show status for all workspaces in the project; container stats are aggregated
  • go — Cd into the workspace directory (only if using via shell wrapper)
  • proxy — Manage the DNS server and HTTP proxy
Options:
  • -p, --project <PROJECT> — name of project [default: The DEVCONCURRENT_PROJECT variable, then the first configured project]

devconcurrent up

Bring up a workspace, creating it if it does not exist

Usage: devconcurrent up [OPTIONS] [WORKSPACE]

Arguments:
  • <WORKSPACE> — Workspace name
Options:
  • -f, --forward — Foward configured forwardPorts once up
  • -d, --detach — Detach worktree rather than creating a branch
  • -b, --branch <BRANCH> — Specify an explicit branch
  • -g, --go — Navigate to the directory after creating (if using via shell wrapper)
  • -x, --exec <EXEC> — Exec once up with the given command [default: the container user’s shell]

devconcurrent exec

Exec into a running devcontainer

Usage: devconcurrent exec [OPTIONS] [CMD]...

Command Alias: x

Arguments:
  • <CMD> — command to run [default: the container user’s shell]
Options:
  • -w, --workspace <WORKSPACE> — Workspace name [default: current working directory]

devconcurrent fwd

Forward configured forwardPorts to a running workspace

Usage: devconcurrent fwd [OPTIONS] [COMMAND]

Command Alias: f

Subcommands:
  • stop — Stop forwarding ports (remove sidecar containers)
Options:
  • -w, --workspace <WORKSPACE> — Workspace name [default: current working directory]

devconcurrent fwd stop

Stop forwarding ports (remove sidecar containers)

Usage: devconcurrent fwd stop

devconcurrent compose

Run docker compose against the given workspace

Usage: devconcurrent compose [OPTIONS] [ARGS]...

Command Alias: c

Arguments:
  • <ARGS> — Arguments to provide to docker compose
Options:
  • -w, --workspace <WORKSPACE> — Workspace name [default: current working directory]

devconcurrent destroy

Fully destroy the workspace; equivalent to docker compose down -v --rmi local --remove-orphans && git worktree remove

Usage: devconcurrent destroy [OPTIONS] [WORKSPACE]

Arguments:
  • <WORKSPACE> — Workspace name
Options:
  • -f, --force — Force remove the worktree, even if dirty

devconcurrent show

Show some value

Usage: devconcurrent show <COMMAND>

Subcommands:
  • ports — Show currently-forwarded ports for this workspace
  • workspace — Print the current workspace name, or exit 1
  • ip — Show container IP addresses for this workspace
  • hostname — Show proxied hostnames for this workspace
  • env — Show this workspace’s configured shell variables
  • ca-root — Print the CA root directory, generating the CA if it isn’t there yet

devconcurrent show ports

Show currently-forwarded ports for this workspace

Usage: devconcurrent show ports

devconcurrent show workspace

Print the current workspace name, or exit 1

Usage: devconcurrent show workspace

devconcurrent show ip

Show container IP addresses for this workspace

Usage: devconcurrent show ip [SERVICE]

Arguments:
  • <SERVICE> — Compose service name; if omitted, list all services for this workspace

devconcurrent show hostname

Show proxied hostnames for this workspace

Usage: devconcurrent show hostname [SERVICE]

Arguments:
  • <SERVICE> — Compose service name; if omitted, list every service in this workspace’s compose configuration

devconcurrent show env

Show this workspace’s configured shell variables

Usage: devconcurrent show env [OPTIONS]

Options:
  • --export <SHELL> — Set the variables in the calling shell, in this shell’s syntax, instead of printing a table.

    With the dc shell function sourced, this takes effect directly. Otherwise the assignments go to stdout for you to eval. Setting shell.exportEnv in config.toml wires this up on every prompt for you.

    Possible values: bash, elvish, fish, powershell, zsh

devconcurrent show ca-root

Print the CA root directory, generating the CA if it isn’t there yet

Trust the CA with dc proxy trust; this command is for importing rootCA.pem somewhere manually.

Usage: devconcurrent show ca-root

devconcurrent status

Show status for all workspaces in the project; container stats are aggregated

Usage: devconcurrent status [OPTIONS]

Command Alias: s

Options:
  • -w, --workspace <WORKSPACE> — Split by container, showing a single workspace
  • -l, --live — Show live, updating data

devconcurrent go

Cd into the workspace directory (only if using via shell wrapper)

Usage: devconcurrent go <WORKSPACE>

Arguments:
  • <WORKSPACE> — Workspace name

devconcurrent proxy

Manage the DNS server and HTTP proxy

Usage: devconcurrent proxy <COMMAND>

Subcommands:
  • up — Start or restart the proxy
  • down — Stop and remove the proxy
  • status — Check that every configured hostname and port is reachable
  • trust — Install the CA into the system and browser trust stores
  • untrust — Remove the CA from the system and browser trust stores

devconcurrent proxy up

Start or restart the proxy

Usage: devconcurrent proxy up [OPTIONS]

Options:
  • -w, --workspace <WORKSPACE> — Workspace name (only useful if its devcontainer.json diverges from the root workspace)

devconcurrent proxy down

Stop and remove the proxy

Usage: devconcurrent proxy down

devconcurrent proxy status

Check that every configured hostname and port is reachable

Usage: devconcurrent proxy status [OPTIONS]

Command Alias: s

Options:
  • -w, --workspace <WORKSPACE> — Workspace name (only useful if its devcontainer.json diverges from the root workspace)
  • -a, --all — Check every proxy-enabled project, not just this one
  • --json — Print the results as JSON instead of a table

devconcurrent proxy trust

Install the CA into the system and browser trust stores

We will do our best to install our CA to your system trust store and, if possible, Firefox and Chromium’s trust stores. This requires root, so you will be asked for your password via sudo.

Under WSL, the CA is also installed into your Windows user certificate store (via certutil.exe), so native Windows browsers trust it too; Windows will ask you to confirm in a security dialog.

Note that our CA is only valid for the listed TLDs (default is only “test”). As long as these aren’t real TLDs that can serve real traffic, this is pretty safe, but it’s still not recommended on a production machine.

Usage: devconcurrent proxy trust

devconcurrent proxy untrust

Remove the CA from the system and browser trust stores

This will also delete the CA and its key.

Usage: devconcurrent proxy untrust

Configuration Reference

Devconcurrent is configured by a simple toml file.

You can override the default path by setting the DEVCONCURRENT_CONFIG environment variable; this should be the path to the directory containing config.toml.

projects.<name>

Configured projects by name.

Type: table

projects.<name>.path

The project location on your host.

Type: string

Required: yes

projects.<name>.worktreeFolder

The directory where devconcurrent will place worktrees. Defaults to workspaces/<project> under the platform data directory. This is also settable in the devcontainer, but it’s available here for projects that don’t use devcontainers.

Type: string

projects.<name>.branch

Template for the default branch name for up on a new workspace, overriding worktree.branch

Type: string

projects.<name>.devcontainer

Any of the options from devcontainer.json (https://containers.dev/implementors/json_reference/), as per-user overrides. These are merged with the project’s devcontainer.json, with arrays concatenated and this file winning conflicts.

Type: table

Example:

If a project uses devcontainers, but not devconcurrent, you might configure it here like this:

[projects.my-app]
path = "~/src/my-app"
# Per-user devcontainer overrides, merged into the repo's devcontainer.json:
devcontainer.customizations.devconcurrent = {
  env = {
    APP_URL = "https://{{ hostname 'app' }}",
    DATABASE_HOST = "{{ hostname 'mysql' }}"
  },
  proxy = {
    enable = true,
    services.app.httpProxyPort = 8080,
  },
}

proxy

Global proxy settings.

Type: table

proxy.enable

Enable the devconcurrent DNS and HTTP proxy.

When false, up never starts the proxy and no root CA is minted, regardless of per-project customizations.devconcurrent.proxy.enable.

Type: boolean

Default: true

proxy.port

The DNS port the proxy listens on.

Type: integer

Default: 43770

proxy.caRoot

Directory holding the root CA used to terminate TLS.

Defaults to devconcurrent/ca in your platform’s data directory.

Type: string

proxy.tlds

TLDs that the proxy may mint certificates for and serve TLS on.

Changing this list regenerates the root CA, which will need to be trusted again.

Type: array<string>

Default: ["test"]

shell

Shell-integration settings.

Type: table

shell.exportEnv

Register a prompt hook to auto-set the variables from customizations.devconcurrent.env based on your current working directory.

Type: boolean

Default: true

worktree

Worktree settings.

Type: table

worktree.branch

Template for the default branch name for up on a new workspace

Type: string

Default: '“{{ workspace }}”'

Example:

[worktree]
# `dc up foo` checks out `plg/foo`.
branch = "plg/{{ workspace }}"

Devcontainer Customization Reference

The devcontainer spec supports customizations; we put them in customizations.devconcurrent. Note that we also support overrides in config.toml, so you could configure devconcurrent, or even a full devcontainer.json in your personal config file for a repository that does not have it set up.

  • worktreeFolder — The directory where devconcurrent will place worktrees.
  • mountGit — Whether to mount the project’s git directory into each workspace’s devcontainer.
  • proxy — Configure DNS hostnames and HTTP proxy.
  • env.<name> — Define shell variables

worktreeFolder

The directory where devconcurrent will place worktrees.

Type: string

mountGit

Whether to mount the project’s git directory into each workspace’s devcontainer.

Git worktrees have a simple .git file that points to the actual .git directory. If that directory isn’t available, then no git commands will work. By mounting it at its original path in the devcontainer, git should just work, both inside and out of the container.

Type: boolean

Default: true

proxy

Configure DNS hostnames and HTTP proxy.

Type: object

proxy.enable

Enable the devconcurrent DNS and HTTP proxy for this project.

Type: boolean

Default: false

proxy.hostname

Handlebars template for the proxied hostname, used by every service that does not set its own.

Available variables:

  • root (bool) — whether this is the root workspace
  • project — project name
  • workspace — workspace name
  • service — name of the service from compose

Type: string

Default: "{{workspace}}.{{service}}.test"

proxy.services.<name>

Configure proxy settings for each docker compose service.

Type: object

proxy.services.<name>.hostname

Handlebars template for this service’s hostname. Overrides the project-level hostname.

Type: string

Example:

You may want a simpler hostname for your primary service.

{
  ...,
  "customizations": {
    "devconcurrent": {
      "proxy": {
        "enable": true,
        "services": {
          "app": {
            "httpProxyPort": 3000,
            "hostname": "{{workspace}.test"
          }
        }
      }
    }
  }
}

proxy.services.<name>.httpProxyPort

If set, devconcurrent will run an HTTP proxy on ports 80 and 443 to this port in your container, performing TLS termination on 443.

If this service runs a web service, put its port here.

Type: integer

env.<name>

Define shell variables

These are rendered by dc show env or automatically set if shell.exportEnv is true.

The values are given by handlebars templates with the following:

  • The hostname helper gives the hostname for a service.
  • The following variables are populated: project, workspace, and root.

Type: string

Example:

{
  "BASE_URL": "{{ hostname 'app' }}",
  "DATABASE_URL": "postgres://postgres:postgres@{{hostname 'postgres'}}:5432/db"
}

Devcontainer Configuration Reference

This is a reproduction of the full devcontainer.json specification, as supported by devconcurrent, with our customizations included. It is formatted as TOML, but the same options can be set as JSON in devcontainer.json.

See the reference spec for more details.

dockerComposeFile

The name of the docker-compose file(s) used to start the services.

Type: string | array<string>

Default: []

service

The service you want to work on. This is considered the primary container for your dev environment which your editor will connect to.

Type: string

runServices

An array of services that should be started and stopped.

Type: array<string>

workspaceFolder

The path of the workspace folder inside the container. This is typically the target path of a volume mount in the docker-compose.yml.

Type: string

shutdownAction

Action to take when the user disconnects from the primary container in their editor. The default is to stop all of the compose containers.

Type: string

Values: "none", "stopCompose"

Default: "stopCompose"

overrideCommand

Whether to overwrite the command specified in the image. The default is false.

Type: boolean

Default: false

$schema

The JSON schema of the devcontainer.json file.

Type: string

name

A name for the dev container which can be displayed to the user.

Type: string

features

Features to add to the dev container. Ignored by devconcurrent.

Type: object

overrideFeatureInstallOrder

Array consisting of the Feature id (without the semantic version) of Features in the order the user wants them to be installed. Ignored by devconcurrent.

Type: array<string>

Default: []

secrets

Recommended secrets for this dev container. Ignored by devconcurrent.

Type: object

forwardPorts

Ports that can be forwarded from the host to the container.

Type: array<integer | string>

Default: []

portsAttributes.<name>

Settings to apply to forwardPorts. Ignored by devconcurrent.

Type: object

portsAttributes.<name>.onAutoForward

Type: string

Values: "notify", "openBrowser", "openBrowserOnce", "openPreview", "silent", "ignore"

Default: "notify"

portsAttributes.<name>.elevateIfNeeded

Type: boolean

Default: false

portsAttributes.<name>.label

Type: string

Default: "Application"

portsAttributes.<name>.protocol

Type: string

Values: "http", "https"

Default: "http"

portsAttributes.<name>.requireLocalPort

Type: boolean

Default: false

otherPortsAttributes

Default settings for forwardPorts. Ignored by devconcurrent.

Type: object

otherPortsAttributes.onAutoForward

Type: string

Values: "notify", "openBrowser", "openBrowserOnce", "openPreview", "silent", "ignore"

Default: "notify"

otherPortsAttributes.elevateIfNeeded

Type: boolean

Default: false

otherPortsAttributes.label

Type: string

Default: "Application"

otherPortsAttributes.protocol

Type: string

Values: "http", "https"

Default: "http"

otherPortsAttributes.requireLocalPort

Type: boolean

Default: false

updateRemoteUserUID

Controls whether on Linux the container’s user should be updated with the local user’s UID and GID. On by default when opening from a local folder.

Type: boolean

containerEnv.<name>

Container environment variables.

Type: string

containerUser

The user the container will be started with. The default is the user on the Docker image.

Type: string

mounts

Mounts to setup on container create.

Type: array<string | object>

Default: []

mounts[].type

Type: string

Values:

  • "bind" — A bind mount from the host filesystem.
  • "volume" — A named Docker volume.

Required: yes

mounts[].source

A string that may contain ${...} variable substitutions. Supported variables: ${localEnv:VAR[:default]} (alias ${env:VAR[:default]}), ${containerEnv:VAR[:default]}, ${localWorkspaceFolder}, ${containerWorkspaceFolder}, ${localWorkspaceFolderBasename}, ${containerWorkspaceFolderBasename}, ${devcontainerId}. https://containers.dev/implementors/json_reference/#variables-in-devcontainerjson

Type: string

mounts[].target

A string that may contain ${...} variable substitutions. Supported variables: ${localEnv:VAR[:default]} (alias ${env:VAR[:default]}), ${containerEnv:VAR[:default]}, ${localWorkspaceFolder}, ${containerWorkspaceFolder}, ${localWorkspaceFolderBasename}, ${containerWorkspaceFolderBasename}, ${devcontainerId}. https://containers.dev/implementors/json_reference/#variables-in-devcontainerjson

Type: string

Required: yes

init

Passes the –init flag when creating the dev container.

Type: boolean

privileged

Passes the –privileged flag when creating the dev container.

Type: boolean

capAdd

Passes docker capabilities to include when creating the dev container.

Type: array<string>

Default: []

securityOpt

Passes docker security options to include when creating the dev container.

Type: array<string>

Default: []

remoteEnv.<name>

Remote environment variables to set for processes spawned in the container including lifecycle scripts and any remote editor/IDE server process.

Type: string

remoteUser

The username to use for spawning processes in the container including lifecycle scripts and any remote editor/IDE server process. The default is the same user as the container.

Type: string

initializeCommand

A command to run locally (i.e Your host machine, cloud VM) before anything else. This command is run before “onCreateCommand”.

Type: string | array<string> | object

initializeCommand.<name>

A command as written in config, before ${...} substitution.

Type: string | array<string>

onCreateCommand

A command to run when creating the container. This command is run after “initializeCommand” and before “updateContentCommand”.

Type: string | array<string> | object

onCreateCommand.<name>

A command as written in config, before ${...} substitution.

Type: string | array<string>

updateContentCommand

A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after “onCreateCommand” and before “postCreateCommand”.

Type: string | array<string> | object

updateContentCommand.<name>

A command as written in config, before ${...} substitution.

Type: string | array<string>

postCreateCommand

A command to run after creating the container. This command is run after “updateContentCommand” and before “postStartCommand”.

Type: string | array<string> | object

postCreateCommand.<name>

A command as written in config, before ${...} substitution.

Type: string | array<string>

postStartCommand

A command to run after starting the container. This command is run after “postCreateCommand” and before “postAttachCommand”.

Type: string | array<string> | object

postStartCommand.<name>

A command as written in config, before ${...} substitution.

Type: string | array<string>

postAttachCommand

A command to run when attaching to the container. This command is run after “postStartCommand”.

Type: string | array<string> | object

postAttachCommand.<name>

A command as written in config, before ${...} substitution.

Type: string | array<string>

waitFor

The user command to wait for before continuing execution in the background while the UI is starting up.

Type: string

Values: "initializeCommand", "onCreateCommand", "updateContentCommand", "postCreateCommand", "postStartCommand"

Default: "updateContentCommand"

userEnvProbe

User environment probe to run.

Type: string

Values:

  • "none" — Do not probe the user’s shell for environment variables.
  • "loginShell" — Probe with a login shell (-lc).
  • "loginInteractiveShell" — Probe with a login, interactive shell (-lic).
  • "interactiveShell" — Probe with an interactive shell (-ic).

Default: "loginInteractiveShell"

hostRequirements

Host hardware requirements.

Type: object

hostRequirements.cpus

Number of required CPUs.

Type: integer

Default: 1

hostRequirements.memory

Amount of required RAM in bytes. Supports units tb, gb, mb and kb.

Type: string

hostRequirements.storage

Amount of required disk space in bytes. Supports units tb, gb, mb and kb.

Type: string

hostRequirements.gpu

Type: boolean | string | object

Default: false

customizations

Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.

Type: object

customizations.devconcurrent

Type: object

customizations.devconcurrent.worktreeFolder

The directory where devconcurrent will place worktrees.

Type: string

customizations.devconcurrent.mountGit

Whether to mount the project’s git directory into each workspace’s devcontainer.

Git worktrees have a simple .git file that points to the actual .git directory. If that directory isn’t available, then no git commands will work. By mounting it at its original path in the devcontainer, git should just work, both inside and out of the container.

Type: boolean

Default: true

customizations.devconcurrent.proxy

Configure DNS hostnames and HTTP proxy.

Type: object

customizations.devconcurrent.proxy.enable

Enable the devconcurrent DNS and HTTP proxy for this project.

Type: boolean

Default: false

customizations.devconcurrent.proxy.hostname

Handlebars template for the proxied hostname, used by every service that does not set its own.

Available variables:

  • root (bool) — whether this is the root workspace
  • project — project name
  • workspace — workspace name
  • service — name of the service from compose

Type: string

Default: "{{workspace}}.{{service}}.test"

customizations.devconcurrent.proxy.services.<name>

Configure proxy settings for each docker compose service.

Type: object

customizations.devconcurrent.proxy.services.<name>.hostname

Handlebars template for this service’s hostname. Overrides the project-level hostname.

Type: string

customizations.devconcurrent.proxy.services.<name>.httpProxyPort

If set, devconcurrent will run an HTTP proxy on ports 80 and 443 to this port in your container, performing TLS termination on 443.

If this service runs a web service, put its port here.

Type: integer

customizations.devconcurrent.env.<name>

Define shell variables

These are rendered by dc show env or automatically set if shell.exportEnv is true.

The values are given by handlebars templates with the following:

  • The hostname helper gives the hostname for a service.
  • The following variables are populated: project, workspace, and root.

Type: string

Example:

{
  "BASE_URL": "{{ hostname 'app' }}",
  "DATABASE_URL": "postgres://postgres:postgres@{{hostname 'postgres'}}:5432/db"
}