Use Docker Compose

Docker Compose provides a way to orchestrate multiple containers that work together. Examples include a service that processes requests and a front-end web site, or a service that uses a supporting function such as a Redis cache. If you are using the microservices model for your app development, you can use Docker Compose to factor the app code into several independently running services that communicate using web requests. This article helps you enable Docker Compose for your apps, whether they are Node.js, Python, or .NET Core, and also helps you configure debugging in Visual Studio Code for these scenarios.

Also, for single-container scenarios, using Docker Compose provides tool-independent configuration in a way that a single Dockerfile does not. Configuration settings such as volume mounts for the container, port mappings, and environment variables can be declared in the docker-compose YML files.

To use Docker Compose in VS Code using the Docker extension, you should already be familiar with the basics of Docker Compose.

Adding Docker Compose support to your project

If you already have one or more Dockerfiles, you can add Docker Compose files by opening the Command Palette (kb(workbench.action.showCommands)), and using the Docker: Add Docker Compose Files to Workspace command. At the prompt, choose the Dockerfiles you want to include and hit Enter.

You can add Docker Compose files to your workspace at the same time you add a Dockerfile by opening the Command Palette (kb(workbench.action.showCommands)) and using the Docker: Add Docker Files to Workspace command. You'll be asked if you want to add Docker Compose files. If you want to keep your existing Dockerfile, choose No when prompted to overwrite the Dockerfile.

The Docker extension adds the following files to your workspace:

Screenshot of project with docker-compose files

The VS Code Docker extension generates files that work out of the box, but you can also customize them to optimize for your scenario. You can then use the Docker Compose Up command (right-click on the docker-compose.yml file, or find the command in the Command Palette) to get everything started at once. You can also use the docker-compose up command from the command prompt or terminal window in VS Code to start the containers. Refer to the Docker Compose documentation about how to configure the Docker Compose behavior and what command-line options are available.

With the docker-compose files, you can now specify port mappings in the docker-compose files, rather than in the .json configuration files. For examples, see the Docker Compose documentation.

Tip: When using Docker Compose, don't specify a host port. Instead, let the Docker pick a random available port to automatically avoid port conflict issues.

Add new containers to your projects

If you want to add another app or service, you can run Add Docker Compose Files to Workspace again, and choose to overwrite the existing docker-compose files, but you'll lose any customization in those files. If you want to preserve changes to the compose files, you can manually modify the docker-compose.yml file to add the new service. Typically, you can cut and paste the existing service section and change the names as appropriate for the new service. You can also

You can run the Add Docker Files to Workspace command again to generate the Dockerfile for a new app. While each app or service has its own Dockerfile, there's one docker-compose.yml and one docker-compose.debug.yml file per project for .NET Core and Python, or one per package.json for Node.js.

In Node.js packages and Python projects, you have the Dockerfile, .dockerignore, docker-compose*.yml files all in the root folder of the workspace. When you add another app or service, move the Dockerfile into the app's folder.

For Python, the situation is similar to Node.js, but there is no docker-compose.debug.yml file.

For .NET, the folder structure is already set up to handle multiple projects when you create the Docker Compose files, .dockerignore and docker-compose*.yml are placed in the workspace root (for example, if the project is in src/project1, then the files are in src), so when you add another service, you create another project in a folder, say project2, and recreate or modify the docker-compose files as described previously.

Debug

First, refer to the debugging documentation for your target platform, to understand the basics on debugging in containers with VS Code:

If you want to debug in Docker Compose, run the command Docker Compose Up using one of the two Docker Compose files as described in the previous section, and then attach using the appropriate Attach launch configuration. Launching directly using the normal launch configuration does not use Docker Compose.

Create an Attach launch configuration. This is a section in launch.json. The process is mostly manual, but in some cases, the Docker extension can help by adding a pre-configured launch configuration that you can use as a template and customize. The process is as follows:

  1. On the Debug tab, choose the Configuration dropdown, choose New Configuration and select the Docker Attach configuration template for appropriate platform. For example, .NET Core Docker Attach (Preview).

  2. Configure the debugging port in docker-compose.debug.yml. This is set when you create the file, so you might not need to change it. In the example below for a Node.js app, port 9229 is used for debugging on both the host and the container.

```yml version: '3.4'

services:
  node-hello:
    image: node-hello
    build: .
    environment:
      NODE_ENV: development
    ports:
      - 3000
      - 9229:9229
    command: node --inspect=0.0.0.0:9229 ./bin/www
```

Python apps do not include the `docker-compose.debug.yml` file.
  1. If you have multiple apps, you need to change the port for one of them, so that each app has a unique port. You can point to the right debugging port in the launch.json, and save the file. If you omit this, the port will be chosen automatically.

    json "configurations": [ { "type": "node", "request": "attach", "name": "Docker: Attach to Node", "remoteRoot": "/usr/src/app", "port": 9229 }, // ... ]

  2. When you choose attach, VS Code asks to choose a container:

Screenshot of attach choose container

To skip this step, specify the container name in the Attach configuration in launch.json:

json "containerName": "Your ContainerName"

  1. VS code tries to copy vsdbg from the host machine to the target container using a default path. For .NET Core, you can also provide a path to an existing instance of vsdbg in the Attach configuration.

json "netCore": { "debuggerPath": "/remote_debugger/vsdbg" }

  1. When done editing the Attach configuration, save launch.json, and select your new launch configuration as the active configuration. In the Debug tab, find the new configuration in the Configuration dropdown.

Screenshot of Configuration dropdown

  1. Launch the debugger in the usual way. From the Debug tab, choose the green arrow (Start button) or use kb(workbench.action.debug.start).

Screenshot of starting debugging

When you attach to a service that exposes an HTTP endpoint that returns HTML, the web browser doesn't open automatically, so you need to start the browser on the host and navigate to the app at http://localhost:{port}, where the port is found by inspecting the .json configuration for the running container as described previously.

Screenshot of debug session

You can also use the docker port command, for example:

docker port apisvc 5000

The command shows the output:

127.0.0.1:49155

The process for configuring debugging is the same for each platform. For .NET Core, the extension generates the launch configuration for attaching to your service. For Python and Node.js, you can copy and modify the following examples. Copy the code into launch.json and modify the settings as needed.

Node.js

Here's an example that shows the Node.js launch configuration - Attach:

    "configurations": [
        {
            "type": "node",
            "request": "attach",
            "name": "Docker: Attach to Node",
            "remoteRoot": "/usr/src/app",
            "port": 9229 // Optional; otherwise inferred from the docker-compose.debug.yml.
        },
        // ...
    ]

Python

Python doesn't have a docker-compose.debug.yml. Here's an example showing the Python launch configuration - Django Attach:

    "configurations": [
        {
            "name": "Docker: Python - Django",
            "type": "docker",
            "request": "launch",
            "preLaunchTask": "docker-run: debug",
            "python": {
                "pathMappings": [
                    {
                        "localRoot": "${workspaceFolder}",
                        "remoteRoot": "/app"
                    }
                ],
                "projectType": "django"
            },
            // ...
        }
    ]

.NET

An example launch configuration for an ASP.NET web app in a Docker container is shown in the following code:

        {
            "name": "Docker .NET Core Attach (Preview)",
            "type": "docker",
            "request": "attach",
            "platform": "netCore",
            "sourceFileMap": {
                "/src": "${workspaceFolder}"
        }

If you try to attach to a .NET Core app running in a container, you'll see a prompt asking if you want to install the debugger (.vsdbg bits into the container).

Screenshot of debugger install prompt

Select your app's container group.

Screenshot of container group selection

Volume mounts

By default, the Docker extension does not do any volume mounting for debugging components. There's no need for it in .NET Core or Node.js, since the required components are built into the runtime. If your app requires volume mounts, specify them by using the volumes tag in the docker-compose*.yml files.

volumes:
    - /host-folder-path:/container-folder-path

Next steps