I recently wrote about My Top VSCode Tips and Features, and one of the VSCode plugins mentioned was the Docker Plugin.

Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications, whether on laptops, data center VMs, or the cloud.

Quite a mouthful, but wikipedia explains it a bit clearly.

Docker is a computer program that performs operating-system-level virtualization also known as containerization.

Essentially, it enables us package our applications into images, and run them as containers on any platform that has docker installed.

The aim for this article is to showcase the various ways the Visual Studio Code Docker plugin makes life easier when working with Docker.

We'll look at 2 developer stacks, NodeJS and Golang.

Installation

You need to have docker installed on your work station. Instructions on how to install and run docker is available here, and it should be specific to the particular Operation System you are running.

You also need to have Visual Studio Code Installed.

Once you have Visual Studio Code installed, open it click on the extensions section on the left most pane, and search for docker.

Once you have reloaded VSCode, you should be good to go.

Once installed, you should notice a few new things in your Visual Studio Code instance. On the left most pane, there's a new docker section with the docker logo, which when clicked opens the Docker Explorer with three sections. Images, Containers, Registries

There are also a few commands added to the command palette, which you can view by opening the command palette and typing in docker

NodeJS

We'll use a simple NodeJS application to demonstrate the capabilities the Docker plugin adds to VSCode.

Let's create a simple express server.

mkdir docker-node
cd docker-node
npm init -y
npm install --save express
touch index.js

We should have a directory tree like this.

.
├── index.js
├── node_modules
├── package-lock.json
└── package.json

1 directory, 3 files

This is the content of index.js

const express = require('express')
const app = express()

app.listen(3000)

app.get('/', (req, res) => {
  res.send('hello world')
})

Update package.json to have a start script.

"scripts": {
    "start": "node index.js"
  },

Now, we can simply run this app with npm start, and go to port 3000 and see the app working.

Traditionally, to add docker, we would follow these steps.

  1. Create a Dockerfile (or docker-compose.yaml)
  2. Add docker instructions to the file (FROM, WORKDIR, ADD, EXPOSE, CMD)
  3. Run docker build... on the terminal to build the image
  4. Run docker run... on the terminal to run the container

With the plugin however, all we need to do is the following.

  1. Open the command palette, and type in docker, then select Docker: Add Docker files to Workspace. It should be the first option. Press Enter You will be asked to choose the platform/stack, select NodeJS and press enter You will then be asked to choose a port. Write 3000 since it's the port our app will listen to. The following files are added to your workspace: .dockerignore, docker-compose.debug.yml, docker-compose.yml and Dockerfile

    The `.dockerignore` tells docker to ignore the files listed when adding files to the build image.

    The docker-compose.debug.yml will allow you to run docker-compose with inspect, and attach a debugger.

version: '2.1'

services:
  docker-node:
    image: docker-node
    build: .
    environment:
      NODE_ENV: development
    ports:
      - 3000:3000
      - 9229:9229
    command: node --inspect=0.0.0.0:9229 index.js

If you are debugging during development though, chances are that you are going to need to attach a volume, so that changes you make on your local machine are persisted in the container.

The docker-compose.yml file is a standard docker-compose file used to run docker-services. When you add other resources/services such as database connections and load balancers, you'll edit this file.

version: '2.1'

services:
  docker-node:
    image: docker-node
    build: .
    environment:
      NODE_ENV: production
    ports:
      - 3000:3000

The Dockerfile, which is the most important here, since it has to be built, contains the instructions we would hae to write manually if we did not have the plugin installed.

FROM node:8.9-alpine
ENV NODE_ENV production
WORKDIR /usr/src/app
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
RUN npm install --production --silent && mv node_modules ../
COPY . .
EXPOSE 3000
CMD npm start
  1. Next, to build the Image, open the VSCode command palette, and type in docker then select Docker: Build Image and press Enter You'll be prompted to select the Dockerfile, choose it and press enter Next you'll be prompted to select the tag. Leave the default docker-node selected and press enter. The Integrated Terminal will open, and the build logs will show.

  2. Finally, we need to run the container. Once again, open the command palette and type in docker run, the select Docker: Run A list of all containers in your system will show up, select docker-node:latest, the one we tagged, and press enter The terminal will show the logs for the run command. Notice it added the -p 3000:3000 exposing the port to our host machine so that we can simply run the application by visiting localhost:3000.

    We can also run the container by going to the left pane, selecting the docker section, then under images, choose the docker-node image, right click and click on run. The same logs will run on the terminal.

    You'll also notice that the images section above has a list of the images in your system. Once the docker-node container is running, We can check the running containers in the same section, and even stop them. Above, I selected Attach Shell, which is equivalent to the docker command below.

docker exec -it <container> sh

Which shows the below terminal log output.

You can see we're in the container and we can list the files inside the container.

Stop the container, and try running the app with docker-compose. Open the command palette, find docker-compose and see the output.

Golang

If you are not familiar with Golang, you can skip to the next topic.

If you read/watch a lot of content about docker, you will usually most likely come a cross a docker container being built.

Docker is also built with golang

Let's create s simple Golang App.

mkdir docker-go
cd docker-go
touch main.go

Your directory tree should have one file.

.
└── main.go

0 directories, 1 file

Here's the content for the main.go file.

package main

import (
    "log"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

func main() {
    http.HandleFunc("/", helloHandler)

    if err := http.ListenAndServe(":9000", nil); err != nil {
        log.Fatalln("ListenAndServer Error", err)
    }
}

You can run the App with

go run main.go

Let's however use the VSCode docker plugin to build an image and run the container.

  1. Create the dockerfile by opening the comand palette, typing in Docker and selecting Docker: Add Dockerfile to Workspace You will be prompted to select platform, choose Go and press enter.

    You'll then be prompted to select a port, write in port 9000, since it's the port we chose on our app, and press enter.

    The following 4 files will be created. .dockerignore, docker-compose.debug.yml, docker-compose.yml and Dockerfile.

    The .dockerignore file tells docker to ignore some files when adding files to the image.

    The docker-compose.debug.yml and the docker-compose.yml are used by docker compose to run the app. They are not that much different, since the debug file requires additional input, as debuging golang is a little bit more complex.

    The Dockerfile here is however the most interesting bit. I've commented out last two lines of the build stage and added RUN go install -v ./...

# RUN go-wrapper download   # "go get -d -v ./..."
# RUN go-wrapper install    # "go install -v ./..."
RUN go install -v ./...

Here's the final docker file.

#build stage
FROM golang:alpine AS builder
WORKDIR /go/src/app
COPY . .
RUN apk add --no-cache git
# RUN go-wrapper download   # "go get -d -v ./..."
# RUN go-wrapper install    # "go install -v ./..."
RUN go install -v ./...

#final stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /go/bin/app /app
ENTRYPOINT ./app
LABEL Name=docker-go Version=0.0.1
EXPOSE 9000

This type of dockerfile pattern is called multi-stage build, and it's main advantage is optimization of docker images. It's mostly useful for compiled languages, which in most cases doesn't require the compilation tools to run the compiled app. Golang is a good example.

In a nutshell, we use part of the docker build to compilt the app, then copy the compiled binary to a lighter docker image and run it from there.

Here's a good video explaining the concept. I know it says (kubernetes best practices), but just watch the video. It's 8 minutes. ;-)

  1. Next we need to build the image. Open the command palette, and type in docker-build, select Docker: Build Image and press enter You'll be prompted to select the Dockerfile, leave the default selected and press Enter Finally, you'll be ask to pick an image tag. Leave the default docker-go:latest and press Enter You'll see the build logs in the integrated terminal.

  2. Lastly, we need to run the container. Open the command palette and type in docker run. Select Docker: Run and press Enter You'll be prompted to select the image. Select docker-go:latest You'll see the logs in the Integrated Terminal Like before, you can also run the container by selecting the docker section in the left pane, and under containers, select docker-go, right click on click on run. Same docker run logs will show.

    Since our our running docker container only had the binary, we can attach the shell, in the containers section. We can the type in ls in the attached shell in the Integrated Terminal, and we'll see a binary file call app, which corresponds to the docker file.

Other Features

We'll finally look at other helpful features that come with the VSCode Docker plugin.

  1. List docker images and containers. We've seen this while working with the above two examples, but here's a screenshot.
  1. docker inspect images: This allows you to inspect the images built and see the details in a json file.

    Select the image you want and open the context menu, and select inspect image. A json file will be opened with the details.

  2. Show container logs: This is also found in the context menu for running containers. We'll use the running nodejs container The logs will be shown in the Integrated Terminal.

  3. Registries: You can login to your docker registry and see the images you've built and pushed.

  4. System Prune: This option allows you to run docker system prune which clears unused images your system. It's available via this button in the docker explorer.

  5. Intellisense: If you have to write the docker files (Dockerfile, docker-compose.yml) yourself, you'll get useful intellisense when typing. It will even give you available image tags. This is triggered by typing in the image name, then a full colon, and CMD + Space.

  6. Dockerfile Linting: When you have an error in your dockerfiles, a squiggly line will appear in VSCode and when you hover over it, you'll be shown what the error is. The problems tab below VSCode will also show it.

Recap

The docker plugin for VSCode can help you quickly setup you create your dockerfiles, build them and run them, without writing down much of the commands yourself.

Play around with it, with the available platforms we did not cover.

Happy Coding!