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:
- It generates completions; we try to have very good shell completions for you.
- It generates the
dcfunction. In addition to letting you type less, this lets devconcurrent change your directory when you ask it to; it’s what powersdc goand similar commands. - 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:
- A top-level
-p/--projectflag. - The
DEVCONCURRENT_PROJECTenvironment variable. - Your current working directory.
- 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:
- You still have to remember what ports things listen on!
- 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↴devconcurrent up↴devconcurrent exec↴devconcurrent fwd↴devconcurrent fwd stop↴devconcurrent compose↴devconcurrent destroy↴devconcurrent show↴devconcurrent show ports↴devconcurrent show workspace↴devconcurrent show ip↴devconcurrent show hostname↴devconcurrent show env↴devconcurrent show ca-root↴devconcurrent status↴devconcurrent go↴devconcurrent proxy↴devconcurrent proxy up↴devconcurrent proxy down↴devconcurrent proxy status↴devconcurrent proxy trust↴devconcurrent proxy untrust↴
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 existexec— Exec into a running devcontainerfwd— Forward configuredforwardPortsto a running workspacecompose— Rundocker composeagainst the given workspacedestroy— Fully destroy the workspace; equivalent todocker compose down -v --rmi local --remove-orphans && git worktree removeshow— Show some valuestatus— Show status for all workspaces in the project; container stats are aggregatedgo— 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 configuredforwardPortsonce 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 todocker 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 workspaceworkspace— Print the current workspace name, or exit 1ip— Show container IP addresses for this workspacehostname— Show proxied hostnames for this workspaceenv— Show this workspace’s configured shell variablesca-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
dcshell function sourced, this takes effect directly. Otherwise the assignments go to stdout for you toeval. Settingshell.exportEnvin 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 proxydown— Stop and remove the proxystatus— Check that every configured hostname and port is reachabletrust— Install the CA into the system and browser trust storesuntrust— 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.projects.<name>.path— The project location on your host.projects.<name>.worktreeFolder— The directory where devconcurrent will place worktrees.projects.<name>.branch— Template for the default branch name forupon a new workspace, overridingworktree.branchprojects.<name>.devcontainer— Any of the options fromdevcontainer.json(https://containers.dev/implementors/json_reference/), as per-user overrides.
proxy— Global proxy settings.proxy.enable— Enable the devconcurrent DNS and HTTP proxy.proxy.port— The DNS port the proxy listens on.proxy.caRoot— Directory holding the root CA used to terminate TLS.proxy.tlds— TLDs that the proxy may mint certificates for and serve TLS on.
shell— Shell-integration settings.shell.exportEnv— Register a prompt hook to auto-set the variables fromcustomizations.devconcurrent.envbased on your current working directory.
worktree— Worktree settings.worktree.branch— Template for the default branch name forupon a new workspace
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.proxy.enable— Enable the devconcurrent DNS and HTTP proxy for this project.proxy.hostname— Handlebars template for the proxied hostname, used by every service that does not set its own.proxy.services.<name>— Configure proxy settings for each docker compose service.proxy.services.<name>.hostname— Handlebars template for this service’s hostname.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.
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 workspaceproject— project nameworkspace— workspace nameservice— 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
hostnamehelper gives the hostname for a service. - The following variables are populated:
project,workspace, androot.
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.service— The service you want to work on.runServices— An array of services that should be started and stopped.workspaceFolder— The path of the workspace folder inside the container.shutdownAction— Action to take when the user disconnects from the primary container in their editor.overrideCommand— Whether to overwrite the command specified in the image.$schema— The JSON schema of the devcontainer.json file.name— A name for the dev container which can be displayed to the user.features— Features to add to the dev container.overrideFeatureInstallOrder— Array consisting of the Feature id (without the semantic version) of Features in the order the user wants them to be installed.secrets— Recommended secrets for this dev container.forwardPorts— Ports that can be forwarded from the host to the container.portsAttributes.<name>— Settings to apply to forwardPorts.otherPortsAttributes— Default settings for forwardPorts.updateRemoteUserUID— Controls whether on Linux the container’s user should be updated with the local user’s UID and GID.containerEnv.<name>— Container environment variables.containerUser— The user the container will be started with.mounts— Mounts to setup on container create.mounts[].typemounts[].source— A string that may contain${...}variable substitutions.mounts[].target— A string that may contain${...}variable substitutions.
init— Passes the –init flag when creating the dev container.privileged— Passes the –privileged flag when creating the dev container.capAdd— Passes docker capabilities to include when creating the dev container.securityOpt— Passes docker security options to include when creating the dev container.remoteEnv.<name>— Remote environment variables to set for processes spawned in the container including lifecycle scripts and any remote editor/IDE server process.remoteUser— The username to use for spawning processes in the container including lifecycle scripts and any remote editor/IDE server process.initializeCommand— A command to run locally (i.e Your host machine, cloud VM) before anything else.initializeCommand.<name>— A command as written in config, before${...}substitution.
onCreateCommand— A command to run when creating the container.onCreateCommand.<name>— A command as written in config, before${...}substitution.
updateContentCommand— A command to run when creating the container and rerun when the workspace content was updated while creating the container.updateContentCommand.<name>— A command as written in config, before${...}substitution.
postCreateCommand— A command to run after creating the container.postCreateCommand.<name>— A command as written in config, before${...}substitution.
postStartCommand— A command to run after starting the container.postStartCommand.<name>— A command as written in config, before${...}substitution.
postAttachCommand— A command to run when attaching to the container.postAttachCommand.<name>— A command as written in config, before${...}substitution.
waitFor— The user command to wait for before continuing execution in the background while the UI is starting up.userEnvProbe— User environment probe to run.hostRequirements— Host hardware requirements.hostRequirements.cpus— Number of required CPUs.hostRequirements.memory— Amount of required RAM in bytes.hostRequirements.storage— Amount of required disk space in bytes.hostRequirements.gpu
customizations— Tool-specific configuration.customizations.devconcurrentcustomizations.devconcurrent.worktreeFolder— The directory where devconcurrent will place worktrees.customizations.devconcurrent.mountGit— Whether to mount the project’s git directory into each workspace’s devcontainer.customizations.devconcurrent.proxy— Configure DNS hostnames and HTTP proxy.customizations.devconcurrent.proxy.enable— Enable the devconcurrent DNS and HTTP proxy for this project.customizations.devconcurrent.proxy.hostname— Handlebars template for the proxied hostname, used by every service that does not set its own.customizations.devconcurrent.proxy.services.<name>— Configure proxy settings for each docker compose service.customizations.devconcurrent.proxy.services.<name>.hostname— Handlebars template for this service’s hostname.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.
customizations.devconcurrent.env.<name>— Define shell variables
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 workspaceproject— project nameworkspace— workspace nameservice— 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
hostnamehelper gives the hostname for a service. - The following variables are populated:
project,workspace, androot.
Type: string
Example:
{
"BASE_URL": "{{ hostname 'app' }}",
"DATABASE_URL": "postgres://postgres:postgres@{{hostname 'postgres'}}:5432/db"
}