I am getting started with devcontainers - I have too many projects, and managing dependencies and versions is getting too tiring. We can automate it with devcontainers.
I work with Java lately, let's start with that. Let's create a project
mkdir HelloWorld
mkdir -p com/example/HelloWorld
touch com/example/HelloWorld/Main.javaAdd some code:
package com.example.HelloWorld;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}Make sure it works:
javac com/example/HelloWorld/Main.java
java com.example.HelloWorld.Main #=> Hello, Java!Note: The convention for java is <namespace>.Mainfile . Mainfile must be a .java file with a public static void main(String[] args) function. Otherwise you get this error:
Error: Main method not found in class com.example.HelloWorld.Main, please define the main method as:
public static void main(String[] args)
Now, let's do it again, but this time with a devcontainer.
devcontainer is a specification. You write json to describe how and what your stack requires. It uses docker, by default. Read more here.
The convention is to create a .devcontainer directory with a devcontainer.json file. My full project:
.
├── .devcontainer
│ └── devcontainer.json
└── com
└── example
└── HelloWorld
└── Main.javaHere is a basic devcontainer.json:
{
"name": "Java DevContainer",
"image": "mcr.microsoft.com/devcontainers/java:20",
"mounts": ["source=${localWorkspaceFolder},target=/workspace,type=bind"],
"workspaceFolder": "/workspace"
}name and image are pretty straight forward. mounts is more interesting. The syntax is
{
"mounts": [
"source=<local-path>,target=<container-path>,type=<mount-type>"
]
}localWorkspaceFolder, when using thedevcontainerCLI, is what you pass to--workspace-folder.targetis the directory in the container where yourlocalWorkspaceFolderwill be linked.typeis eitherbindorvolume.bindis what we want for local development.
Build it!
devcontainer build --workspace-folder . --no-cacheNow the container is built. That is where your code will run, and you will do development.
Now we start the container.
devcontainer up --workspace-folder . -- bashFinally, we can "go into" the container with exec:
devcontainer exec --workspace-folder . -- bashWe are in /workspace by default. That comes from the "workspace" folder in the devcontainer.json file. The user is vscode by default, at least on my machine.
vscode ➜ /workspace $ pwd
/workspace
vscode ➜ /workspace $ tree -a .
.
├── com
│ └── example
│ └── HelloWorld
│ └── Main.java
└── .devcontainer
└── devcontainer.json
5 directories, 2 files
We can simply edit the files in the container using vim , or even using your own IDE - the changes will be synced both ways, since we are using type=bind in mounts.
You can use VS Code to alleviate the need do build / start / exec the container with the devcontainer extension.
First, stop the existing container. Mine is 5e7c52cc282b. You can do it using docker. devcontainer CLI seems to lack this feature.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5e7c52cc282b mcr.microsoft.com/devcontainers/java:17 "/bin/sh -c 'echo Co…" 20 minutes ago Up 20 minutes quirky_ramanStop it:
docker container stop 5e7c52cc282b