Skip to content

Commit

Permalink
Merge pull request #217 from bitovi/docker-syntax-highlighting
Browse files Browse the repository at this point in the history
Misc Docker Content Updates
  • Loading branch information
ConnorGraham authored Dec 22, 2020
2 parents 1b7ae40 + 59ddf64 commit 464504e
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 83 deletions.
6 changes: 3 additions & 3 deletions src/docker/1-what-is-docker/what-is-docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@
<iframe width="560" height="315" src="https://www.youtube.com/embed/eD2xZZis2GI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Overview
Traditional application deployment requires packaging application source code into an artifact and deploying it to a server that has a compatible operating system, runtime and other dependant libraries.
Traditional application deployment requires packaging application source code into an artifact. An artifact is the output of the transformation from application source code to a runnable asset. In the case of NodeJS, it is Javascript with dependencies installed (node_modules). The artifact is deployed to a server that has a compatible operating system, runtime and other dependent libraries.

Docker exists to address these issues. Docker bundles runtime dependencies with application source code into an image - creating a unified experience whether an application is being run on a developer's workstation or a production server.

## VMs vs Docker Containers
Virtual machines (VM) are an abstraction of a physical server turning one server into many. A hypervisor is installed on the host server allowing multiple VMs to run on a single machine. Each VM includes a full copy of an operating system (OS) making it resource intensive to run and slow to boot.
[Virtual machines](https://www.vmware.com/topics/glossary/content/virtual-machine) (VM) are an abstraction of a physical server turning one server into many. A [hypervisor](https://www.vmware.com/topics/glossary/content/hypervisor) is installed on the host server allowing multiple VMs to run on a single machine. Each VM includes a full copy of an operating system (OS) making it resource intensive to run and slow to boot.

Containers are an abstraction at the app layer that packages application artifacts and dependencies together. The fundamental difference is containers share the same host operating system, but each container runs in it's own isolated process controlled by the Docker Engine. Containers are more lightweight than VMs and typically boot in seconds instead of minutes.

![Docker Architecture](../static/img/docker/1-what-is-docker/docker-arch.png)

## Dockerfiles, Images and Containers
A Dockerfile is used to build a Docker image. It is a plain-text file that contains a series of instructions telling Docker what operating system, application dependencies and application source code is required to run the application.
A Dockerfile is used to build a Docker image. A dockerfile is a plain-text file that contains a series of instructions telling Docker what operating system, application dependencies and application source code is required to run the application.

A Docker image is a static artifact that is built from a Dockerfile and is tagged and published to a registry where it can be shared.

Expand Down
16 changes: 9 additions & 7 deletions src/docker/2-build-node-app/build-node-app.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@page learn-docker/build-node-app Build a Node App
@page learn-docker/build-node-app Build a NodeJS App
@parent learn-docker 2

@description Build a simple Express API to use for the rest of the course.
Expand All @@ -8,17 +8,18 @@
<iframe width="560" height="315" src="https://www.youtube.com/embed/6sHuGWj5cGM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Overview
In this section, we are going to build a simple "Hello World" api using [Express](https://expressjs.com/).
In this section, we are going to build a simple "Hello World" service using [Express](https://expressjs.com/).

NodeJS is not required if you just want to read through this section and copy/paste the code snippets. If you want to run the application before we containerize it in the next section, you can install NodeJS [here](https://nodejs.org/en/download/).
NodeJS is not required if you just want to read through this section and copy/paste the code snippets. If you want to run the application natively before we containerize it in the next section, you can install NodeJS [here](https://nodejs.org/en/download/).

## Building our app
The app requires only two files
* `package.json`
* `src/index.js`

### package.json
```
✏️ Create `package.json` in the root of your repository.
```json
{
"name": "bitovi-academy-app",
"version": "1.0.0",
Expand All @@ -39,7 +40,8 @@ This file defines `express` as a standard dependency and `nodemon` as a dev depe
Our `package.json` also defines a `start` script and a `start:prod`. These are convenient ways to allow us to start our application with nodemon or node by running `npm start` or `npm run start:prod`.

### src/index.js
```
✏️ Create a `src` directory and `index.js` within it.
```js
const express = require('express')
const app = express()
const port = process.env.PORT || 3000
Expand All @@ -55,8 +57,8 @@ app.listen(port, () => {
This file starts an express server listening on `localhost:3000`. If we access the root path it should return `Hello World!`. The default port is 3000, but can be overwritten by setting the `PORT` environment variable before starting the server.

## Run our app
This step is optional, but to verify our app works you can run
```
✏️ This step is optional, but to verify our app works you can run:
```bash
npm install
npm start
```
Expand Down
4 changes: 2 additions & 2 deletions src/docker/3-writing-a-dockerfile/script.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ EXPOSE $PORT
CMD npm start

### FROM node:15
the first line of any docker file as a from instruction because we are looking to containerize a node js app our from instruction will be from node:15. The node part specifies the name of the image we want to use the: 15 part is a tag and specifies the version of the image we want to use in this case nodes 15.
the first line of any docker file as a from instruction because we are looking to containerize a NodeJS app our from instruction will be from node:15. The node part specifies the name of the image we want to use the: 15 part is a tag and specifies the version of the image we want to use in this case nodes 15.

The Node 15 image contains NPM, node some other helpful packages for building and running a node app. Using a base image like this allows the rest of her docker file to focus explicitly on logic specific to our application.
The node 15 image contains NPM, node some other helpful packages for building and running a NodeJS app. Using a base image like this allows the rest of her docker file to focus explicitly on logic specific to our application.

Because the node image is in itself a docker image it also has its own Docker file. Because it's a Docker file it has its own from statement this hierarchy of our Docker file depending on no JS that depends on another base image repeats all the way down until we reach a special base image called scratch scratch is just a base image that contains what's required for Docker to function at all.

Expand Down
30 changes: 18 additions & 12 deletions src/docker/3-writing-a-dockerfile/writing-a-dockerfile.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
@page learn-docker/writing-a-dockerfile Writing a Dockerfile
@parent learn-docker 3

@description Write a Dockerfile to containerize the node app.
@description Write a Dockerfile to containerize the NodeJS app.

@body

<iframe width="560" height="315" src="https://www.youtube.com/embed/LiAkpRc6z0Y" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Our Dockerfile
Starting with the solution, the `Dockerfile` for our NodeJS app should look like this:
```
Starting with the solution, the `Dockerfile` for our NodeJS app should look like this.

✏️ Copy and paste this to the root of your project and name the file `Dockerfile`:
```dockerfile
FROM node:15

ARG PORT=8000
Expand All @@ -23,11 +25,15 @@ RUN npm install
EXPOSE $PORT
CMD npm start
```
Copy and paste this to the root of your project and name the file `Dockerfile`.

Let's break down each line.
## FROM instruction
The `FROM` instruction is the first line of any Dockerfile. It sets the base image to be used as a starting point for all other instructions. Using base images allows deferring installation of a kernel and low level packages to the provider of the base image.
The `FROM` instruction is the first line of any Dockerfile. It sets the base image to be used as a starting point for all other instructions. Using base images allows deferring:

- installation of a kernel and
- installtion of low level packages

to the provider of the base image.

In our case, We are using `node:15`. `node` specifies the name of the base image and `15` is a tag specifying the version of the image. This base image has the latest version of NodeJS 15 pre-installed, allowing the rest of the `Dockerfile` to focus on logic specific to our application. The `node:15` image also has a `Dockerfile` which means it also has a `FROM` instruction. `node:15` uses [Debian](https://hub.docker.com/_/debian) as its base image. This layering of Docker images repeats until a Dockerfile uses `FROM scratch` as its base image.

Expand All @@ -43,11 +49,11 @@ In our case, `ARG PORT=8000` is defining an argument called `PORT` with a defaul
[Official Docs](https://docs.docker.com/engine/reference/builder/#arg)

## ENV instruction
The `ENV` instruction is used to define environment variables. Like `ARG`, it follows the syntax `ENV <name> [=<default value>]`. The difference between `ARG` and `ENV` is `ENV` can be used during the image build process and also when the container is running, but only `ARG` can be overwritten during the build process. `ENV` can also be overwritten at run time with the `-e` cli argument or an env file ([Docs](https://docs.docker.com/engine/reference/run/#env-environment-variables)).
The `ENV` instruction is used to define environment variables. Like `ARG`, it follows the syntax `ENV <name> [=<default value>]`. The difference between `ARG` and `ENV` is that variables defined with the `ENV` instruction can be used during the image build process and also when the container is running as standard environment variables. However, only `ARG` defined variables can be actually overwritten during the build process with the `--build-arg` cli argument. `ENV` can be overwritten at run time with the `-e` cli argument or an env file ([Docs](https://docs.docker.com/engine/reference/run/#env-environment-variables)).

To give us the flexibility to set and use variables at build time and run time, we use both `ARG` and `ENV` instructions together:

```
```dockerfile
ARG PORT=8000
ENV PORT=$PORT
```
Expand Down Expand Up @@ -89,7 +95,7 @@ ENV PORT=$PORT
The `WORKDIR` instruction sets the working directory for any subsequent instructions. If the directory does not exist, it will be automatically created.

The `WORKDIR` instruction can be used multiple times.
```
```dockerfile
WORKDIR x
WORKDIR y
WORKDIR z
Expand All @@ -103,14 +109,14 @@ In our case, `WORKDIR app` will create the `/app/` directory for us to copy our

## COPY instruction
The `COPY` instruction follows the syntax `COPY [--chown=<user>:<group>] <src>... <dest>`.
- `--chown=<user>:<group>` allows setting the permissions of the target file or directory using [chown](https://linux.die.net/man/1/chown). This is only available to Linux based images
- `--chown=<user>:<group>` allows setting the ownership of the target file or directory using [chown](https://linux.die.net/man/1/chown). This is only available to Linux based images
- `<src>...` specifies what file(s) or directories should be copied in to the docker image.
- Each file/directory should be separated by a space (` `)
- Each entry supports wildcards and matching. For example: `COPY hom* .` Will match all files starting with "hom"
- `<dest>` specifies where each src entry should be copied to in the docker image. `.` will put each entry in the `WORKDIR` or the root directory if no `WORKDIR` is specified.

In our case, we have
```
```dockerfile
WORKDIR app
COPY src src
COPY package.json .
Expand All @@ -132,12 +138,12 @@ In our case, we are using `RUN npm install` to install dependencies from the `pa
`EXPOSE` specifies which ports the when running the container. Each port is separated by a space (` `). By default the container listens using TCP, but UDP can be used by adding `/udp` after the port.

For example:
```
```dockerfile
EXPOSE 80/udp
```

In our case, we want the port exposed by our container to match the port our application is running on (remember: `const port = process.env.PORT || 3000` in `src/index.js`), therefore, we are publishing the value of our `PORT` environment variable.
```
```dockerfile
ARG PORT=8000
ENV PORT=$PORT
...
Expand Down
26 changes: 13 additions & 13 deletions src/docker/4-build-and-run-image/build-and-run-image.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
@page learn-docker/build-and-run-image Build and Run An Image
@parent learn-docker 4

@description Build an image and run a container for our node app.
@description Build an image and run a container for our NodeJS app.

@body

<iframe width="560" height="315" src="https://www.youtube.com/embed/sQ4TG6cfSfw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## App Review
In the last section, we created a `Dockerfile` for our NodeJS app.
```
```dockerfile
FROM node:15

ARG PORT=8000
Expand All @@ -29,14 +29,14 @@ At this point, our directory should look like this

## Build our image
Ensure you have docker desktop running on your machine, then open a terminal to your application directory and run
```
```bash
docker build -t my-node-app .
```
- The `-t my-node-app` argument tells Docker to call the image produced by the `Dockerfile` "my-node-app" and tag it as "latest". The "latest" tag is the default tag, but can be overridden when building an image with `:<tagname>`. For example, if we wanted to tag our image as `v1.0.0`, we would run `docker build -t my-node-app:v1.0.0 .`.
- The `.` argument tells Docker where it can find our `Dockerfile`.

Running the above command should produce output similar to below
```
```bash
$ docker build -t my-node-app .
Sending build context to Docker daemon 4.608kB
Step 1/9 : FROM node:15
Expand Down Expand Up @@ -82,7 +82,7 @@ Successfully tagged my-node-app:latest

## View our image
The `my-node-app:latest` image has been saved to our local Docker registry. Run `docker image ls` and you can see information about it.
```
```bash
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
my-node-app latest 848464f1725a 5 seconds ago 944MB
Expand All @@ -92,7 +92,7 @@ You'll notice there's also a `node:15` image in our registry. This is because we

## Run our image
We're finally ready to run our image. Open a terminal and run
```
```bash
docker run --name my-container -p 8000:8000 -d my-node-app:latest
```
- `--name my-container` gives our container a friendly name. If this is not provided, Docker will name the container for you.
Expand All @@ -101,21 +101,21 @@ docker run --name my-container -p 8000:8000 -d my-node-app:latest
- `my-node-app:latest` specifies what image we want to run. If this image does not exist in our local registry, Docker will try and find and download a match from [Dockerhub](https://hub.docker.com/).

Running the above command should produce output similar to below
```
```bash
$ docker run --name my-container -p 8000:8000 -d my-node-app:latest
71cfe418a26a05bbebb160f541e53c025157dc7ee2c333ab2309c7bc66bb24e3
```
Open your browser to `localhost:8000` and you should see `Hello World!`. You just ran your first docker image!

## Interacting with our container
Run `docker ps` to see the status of all running containers
```
```bash
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
71cfe418a26a my-node-app:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:8000->8000/tcp my-container
```
You can view logs with `docker logs <container-name>`
```
```bash
$ docker logs my-container

> bitovi-academy-app@1.0.0 start
Expand All @@ -129,7 +129,7 @@ $ docker logs my-container
Example app listening at http://localhost:8000
```
Finally, stop our container with `docker stop <container-name>` and `docker rm <container-name>`. If you want to do this with one command, just run `docker rm -f <container-name>`.
```
```bash
$ docker rm -f my-container
my-container

Expand All @@ -140,7 +140,7 @@ CONTAINER ID IMAGE COMMAND CREATED

## Customize the port
We can use the `-e` flag when starting our container to set the `PORT` environment variable.
```
```bash
$ MY_PORT=9000
$ docker run --name my-container -p 8000:$MY_PORT -d -e PORT=$MY_PORT my-node-app:latest
0c0a51e7a19f37d503452892df9498de18c5dc78719aae2511b42a32f1f734ad
Expand All @@ -160,8 +160,8 @@ Example app listening at http://localhost:9000
You'll see the last line of the logs indicating the app is now listening on port 9000. However, because we set the port mapping with `-p 8000:9000`, we still will view the application in our browser from `localhost:8000`.

## Review
We've built an image and run a container for our node app. Here's a cheat sheet of all the commands we ran.
```
We've built an image and run a container for our NodeJS app. Here's a cheat sheet of all the commands we ran.
```bash
# Build an image
docker build -t my-node-app .

Expand Down
Loading

0 comments on commit 464504e

Please sign in to comment.