Home Building non-containers with Docker and VS Code
Post
Cancel

Building non-containers with Docker and VS Code

In this post:

  • example configuration
  • command line breakdowns
  • links to relevant documentation

I recently set up containerised builds for my website [It’s not quite ready yet.] and thought I’d share the configuration for others to use as reference.

💁‍♂️ Note that I’m using Docker mainly to avoid installing tooling globally. If this isn’t the case for you, then a different build strategy will likely be better.

Additionally, some command lines will contain placeholders with example content {EX:like this} and optional segments [OPT:like this]. You will have to at least remove the respective parentheses and EX: or OPT: to make them work.

.vscode/tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format¹
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Create {EX:target/bundle/schichler-dev}/",
            "type": "shell",
            "command": "mkdir --parents {EX:target/bundle/schichler-dev}",
            "windows": {
                "command": "if not exist {EX:target/bundle/schichler-dev}/NUL mkdir {EX:target\\bundle\\schichler-dev}"
            }
        },
        {
            "label": "Build {EX:schichler-dev}",
            "dependsOn": [
                "Create {EX:target/bundle/schichler-dev}/"
            ],
            "type": "shell",
            "command": "docker build -t {EX:schichler-dev} -f {EX:schichler-dev/Dockerfile} {EX:.} && docker run [OPT:--read-only] --mount type=bind,source=${workspaceFolder}{EX:/target/bundle/schichler-dev},destination=/mnt/target --rm {EX:schichler-dev}",
            // The task group shown by Tasks: Run Build Task.
            "group": "build",
            "presentation": {
                // Automatically show the problems panel if any problems are matched.
                // The default here is "never".
                "revealProblems": "onProblem"
            },
            // Prevents Code's prompt whether to show output.
            // This was added automatically when I selected never to.
            // You can add problem matchers² to make Code recognise
            "problemMatcher": []
        }
    ]
}

¹ Integrate with External Tools via Tasks; comment from default tasks.json.
² See # Processing task output with problem matchers.

This creates the following entry in the Tasks: Run Build Task command menu, by default bound to Ctrl + Shift + B:
[Select the build task to run] > Build schichler-dev
…which runs the following shell commands in dependent sequence:

1
2
docker build -t {EX:schichler-dev} -f {EX:schichler-dev/Dockerfile} {EX:.}
docker run [OPT:--read-only] --mount type=bind,source=${workspaceFolder}{EX:/target},destination=/mnt/target --rm {EX:schichler-dev}

In my case:

  1. Make the directory (mkdir) path (--parents) target/bundle/schichler-dev, if it doesn’t exist yet (--parents).
    💁‍♂️ Windows requires special handling here, since Windows’s MKDIR will error if the directory already exists, isn’t configurable in that regard, and also doesn’t support / as path separator.
    CMD /E:ON /C makes sure command extensions are on (default since 2000/XP) so we can create the entire path at once.
    if not exist target/bundle/schichler-dev/NUL makes sure target/bundle/schichler-dev doesn’t exist as a directory already, by checking for the special file NUL within it.
    See https://stackoverflow.com/a/4165472.
    2.
    1. Build a Docker image and tag (-t) it as schichler-dev(:latest).
      Use the Docker file (-f) located at schichler-dev/Dockerfile³ and use .⁴ as context root.
    2. Run the Docker image tagged schichler-dev with its internal file system read-only (--read-only)⁵, while mounting (--mount) ${workspaceFolder}/target/bundle/schichler-dev⁶ mutably inside the container as /mnt/target. Remove (--rm) the container instance afterwards.

³ You don’t need to specify this for a Dockerfile in the context root.
⁴ The folder opened in Code is the working directory for the command.
⁵ This isn’t strictly necessary, but Docker may be able to start the container a little faster if it has a matching optimisation.
⁶ Full paths are required here.
💁‍♂️ Code handily expands ${workspaceFolder} to what we need. Show the suggestions (default: Ctrl + Space) within the "command" parameter’s value (but only before the first such expansion, it seems) for a list of other values that can be interpolated.

Dockerfile

End your (Unix) Dockerfile with:

1
2
CMD rm -fr /mnt/target/* \
    && cp -vr --no-target-directory {EX:bundle} /mnt/target

This in my case:

  1. Removes (rm), recursively (-r), all files and directories (*) inside the mounted host directory.

  2. Copies (cp), recursively (-r), the contents of (--no-target-directory) my build directory inside the container (bundle) into the mounted host directory visible at /mnt/target inside the container.
    💁‍♂️ Instead of writing --no-target-directory, you could append /* to the source directory to use shell expansion. I don’t know whether this makes a practical difference.

Running the Task

You can now run the build task by pressing Ctrl + Shift + B and then Enter.

Code should show the output of the command in a new task terminal window.

This post is not licensed for any purpose, unless otherwise noted.
It is provided AS IS without any guarantee of correctness beyond those required for legal reasons.