Docker within Microsoft VS Code
Welcome back! So here we are inside VS Code; by the way, if your VS Code terminal does not open with the bottom terminal, you can open the terminal by going to View (top toolbar) -> Terminal.
Let’s take a look around! On the far left, we have all of the files contained in our current folder — or we would if we had any files to show! At this point, our folder is empty. Under that, there are tabs for Search, Source Control, Run and Debug, Remote Explorer, Extensions, and Docker. Simply typing “docker” into the terminal results in a long list of options, commands, management commands, and tips on using the “docker” command.
Let’s go ahead and start playing around with Docker containers. If we create a file in our folder and simply title it “dockerfile”, VS Code automatically recognizes the filetype and places a tiny Docker whale next to the file name. But running a blank file won’t do much — let’s go ahead and populate the file with some useful information! The basic layout of the Dockerfile will be as follows:
- Identify an operating system or environment. After all, we need a starting point!
- Place our name and relevant information in the file so other people know who generated the file.
In an earlier post, we discussed how an image can run without a container, but a container cannot run without an image! So here we are — assigning an image for the container to run on. But which image do we choose? Luckily, Docker already has a significant number of images we can choose from in its Docker Hub. As you can see, there are different operating systems (OS), different flavors of each OS, and different metrics for how trustworthy the content is!
Since Alpine is first on the list, let’s use that. If we click on Alpine, we’ll see all the different flavors we can choose from. Furthermore, I like pie, so we’ll go ahead and choose the “3.14” flavor.
Now let’s enter that into our Dockerfile, appropriately named “dockfile”. The Dockerfiler should now read as follows:
FROM alpine:3.14 AS build maintainer = "Aaron Pung (5/27/2022)" LABEL version = "1.0" LABEL description = "Our first Docker file!"
Now, Docker has a file to read, and commands to execute. Let’s go ahead and build this file using the build command: docker build .
vscode ➜ /com.docker.devenvironments.code $ docker build . [+] Building 2.9s (5/5) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 182B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/alpine:3.14 2.8s => CACHED [1/1] FROM docker.io/library/alpine:3.14@sha256:06b5d462c92fc39303e6363c65e074559f8d6b1363250027ed5053557e3398c5 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:bfb5505f953ee93520f53ea44c322f78e353f9dd4dff343bd9c26d58a12f38ea
There’s a lot going on in this image, so let’s pick it apart piece by piece:
- FROM alpine:3.14 AS build
- FROM imports an image. In our case, our image, Alpine, will be automatically downloaded from Docker Hub. When it does so, it will explicitly look for version 3.14.
- During the import, the specified flavor of Alpine is defined as a “stage” named “build” using the AS command.
- This not only hints that we can have multiple stages in one Dockerfile, but we can also use this stage definition to specify commands later on.
- LABEL maintainer = "Aaron Pung (5/27/2022)"
- LABEL identifies that we will be entering a key (“maintainer”) and value (name, date) pair. The pair will stick with the image as metadata after we build our container.
We can easily un these commands through Docker using the VS Code terminal using the docker build . command without ever specifying the filename or an explicit location. The . at the end of our command tells Dockerfile to look in the current folder for a default file named “dockerfile”.
Okay, but what’s going on in the terminal? As Docker starts building the container, it begins by loading the build, docker ignore file, and metadata for the image we chose (Alpine 3.14). When it looks around for the image, it realizes that we have not downloaded the image file from Docker Hub, and sets out to download the image for us. In VS Code, the majority of the text will be blue, including all the SHA codes. After the Alpine image is downloaded, Docker exports all the information it’s gathered into a single image. During this process, it writes the different layers of the container (a topic for another time), before finally writing our image.
Docker seems to have successfully written an image file, but what is the image called? Let’s take another look at the images and containers that exist before and after we build our container. We can clearly see the creation of one image, and zero containers due to the build we just performed!
Associated with this build, we find a lot of useful information. The image we’ve generated is associated with a repository, a tag, an image ID, and information about its creation date and size. For the image we just created, the first two are “<none>”, which is pretty unhelpful. Let’s go ahead and assign a name and tag by altering our build command to:
docker build --no-cache -t firstfile:v0 "."
This command is a bit more complicated than the previous build command, but contains a few more specification options. For instance, the --no-cache argument stops Docker from using any packages that may be stored in the computer’s cache from a previous build. This can be a good thing if you want to make sure all your packages stay up to date! Without this command, Docker will have the option to not re-search the image repository for the newest version of the image we are using as a base.
The next portion, -t firstfile:v0 specifies that we would like to assign a tag (-t) to the image we build. Furthermore, the image will be listed with a repository “firstfile” and a tag of “v0”. Lastly, Docker should look in the current directory for a Docker file named Dockerfile. Okay, great! Now we have an image and its associated repository:tag combination. We can run the image with Docker’s run command:
docker run -it firstfile:v0
It’s worth noting that we could also run the same image using the image ID (docker run -it bfb5505f953e). The -it flag is actually two flags combined into one; the i portion tells Docker to run the image interactively, while the t portion tells Docker to place a pseudo-tty (terminal) inside the container. Together, we’re able to poke around inside the container and see what’s in there!
vscode ➜ /com.docker.devenvironments.code $ docker run -it firstfile:v0 / # ls bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
Executing the run command, Docker places us inside the container at a command line. The / tells us we’re in the base directory, and the # tells us we’re there with admin (root) privileges. A simple ls command shows us all the folders we have at our disposal.
At this point, we’ve created a Docker file, used the file to build our first image, found the image attributes, and used those attributes to enter the image and look at the folders that come with the 3.14 flavor of our Alpine image! From here, it is just a matter of continuing to build functionality into the dockerfile script to fulfill the ultimate goal of our container.
In the next tutorial, we’ll look at running our dockerfile with a different tool called Docker Compose. In the meantime, I hope you found this content helpful!
Get new content delivered directly to your inbox.