DEV Community

loading...
Cover image for a first look at docker

a first look at docker

anthony-campolo
full stack web dev
Updated on ・7 min read

Docker is a set of tools that use OS-level virtualization to deliver software in isolated packages called containers. Containers bundle their own software, libraries and configuration files. They communicate with each other through well-defined channels and use fewer resources than virtual machines.

The code for this article is available on my GitHub.

Outline

  1. Create a Node project with an Express server
    • Initialize project and install dependencies
    • Create server
    • Run server
  2. Create a container image
    • Create Dockerfile
    • Create .dockerignore
  3. Build image
    • Build project with docker build
    • List Docker images with docker images
  4. Run image
    • Run Docker container with docker run
    • List containers with docker ps
    • Print output of your app with docker logs
    • Call your app using curl
  5. Create docker-compose.yml file
    • Create and start containers with docker compose up
  6. Push your project to a GitHub repository
    • Create a new blank repository
    • Create .gitignore file
    • Initialize Git and push to newly created repo
  7. Publish to GitHub Container Registry
    • Login to ghcr.io with docker login
    • Tag image
    • Push to registry
    • Pull your image

1. Create a Node project with an Express server

We will create a simple Node application with Express that returns an HTML fragment.

Initialize project and install dependencies

mkdir ajcwebdev-docker
cd ajcwebdev-docker
npm init -y
npm i express
touch index.js
Enter fullscreen mode Exit fullscreen mode

Create server

Enter the following code into index.js.

// index.js

const express = require("express")
const app = express()

const PORT = 8080
const HOST = '0.0.0.0'

app.get('/', (req, res) => {
  res.send('<h2>ajcwebdev-docker</h2>')
})

app.listen(PORT, HOST)
console.log(`Running on http://${HOST}:${PORT}`)
Enter fullscreen mode Exit fullscreen mode

Run server

node index.js
Enter fullscreen mode Exit fullscreen mode
Listening on port 8080
Enter fullscreen mode Exit fullscreen mode

01-localhost-8080-ajcwebdev-docker

2. Create a container image

You'll need to build a Docker image of your app to run this app inside a Docker container using the official Docker image. We will need two files: dockerfile and .dockerignore.

Create a Dockerfile

Docker can build images automatically by reading the instructions from a Dockerfile.

touch Dockerfile
Enter fullscreen mode Exit fullscreen mode

A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.

FROM

The FROM instruction initializes a new build stage and sets the Base Image for subsequent instructions. A valid Dockerfile must start with FROM.

FROM node:14-alpine
Enter fullscreen mode Exit fullscreen mode

The first thing we need to do is define from what image we want to build from. We will use version 14-alpine of node available from Docker Hub because the universe is chaos and you have to pick something so you might as well pick something with a smaller memory footprint.

LABEL

The LABEL instruction is a key-value pair that adds metadata to an image.

LABEL org.opencontainers.image.source https://github.com/ajcwebdev/ajcwebdev-docker
Enter fullscreen mode Exit fullscreen mode

WORKDIR

The WORKDIR instruction sets the working directory for your application to hold the application code inside the image.

WORKDIR /usr/src/app
Enter fullscreen mode Exit fullscreen mode

COPY

This image comes with Node.js and NPM already installed so the next thing we need to do is to install your app dependencies using the npm binary. The COPY instruction copies new files or directories from <src>.

COPY package*.json ./
Enter fullscreen mode Exit fullscreen mode

The COPY instruction bundles your app's source code inside the Docker image and adds them to the filesystem of the container at the path <dest>.

RUN

The RUN instruction will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile.

RUN npm i
COPY . ./
Enter fullscreen mode Exit fullscreen mode

Rather than copying the entire working directory, we are only copying the package.json file. This allows us to take advantage of cached Docker layers.

EXPOSE

The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime.

EXPOSE 8080
Enter fullscreen mode Exit fullscreen mode

Your app binds to port 8080 so you'll use the EXPOSE instruction to have it mapped by the docker daemon.

CMD

Define the command to run your app using CMD which defines your runtime. The main purpose of a CMD is to provide defaults for an executing container.

CMD ["node", "index.js"]
Enter fullscreen mode Exit fullscreen mode

Here we will use node index.js to start your server.

Complete Dockerfile

Your Dockerfile should now look like this:

FROM node:14-alpine
LABEL org.opencontainers.image.source https://github.com/ajcwebdev/ajcwebdev-docker
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm i
COPY . ./
EXPOSE 8080
CMD [ "node", "index.js" ]
Enter fullscreen mode Exit fullscreen mode

Create .dockerignore

Before the docker CLI sends the context to the docker daemon, it looks for a file named .dockerignore in the root directory of the context. Create a .dockerignore file in the same directory as your Dockerfile.

touch .dockerignore
Enter fullscreen mode Exit fullscreen mode

If this file exists, the CLI modifies the context to exclude files and directories that match patterns in it. This helps avoid sending large or sensitive files and directories to the daemon.

node_modules
Dockerfile
.dockerignore
.git
.gitignore
npm-debug.log
Enter fullscreen mode Exit fullscreen mode

This will prevent your local modules and debug logs from being copied onto your Docker image and possibly overwriting modules installed within your image.

3. Building your image

Go to directory with Dockerfile and build the Docker image. The -t flag lets you tag your image so it's easier to find later using the docker images command:

Build project with docker build

The docker build command builds an image from a Dockerfile and a "context". A build’s context is the set of files located in the specified PATH or URL. The URL parameter can refer to three kinds of resources:

  • Git repositories
  • Pre-packaged tarball contexts
  • Plain text files
docker build . -t ajcwebdev/ajcwebdev-docker
Enter fullscreen mode Exit fullscreen mode

List Docker images with docker images

Your image will now be listed by Docker. The docker images command will list all top level images, their repository and tags, and their size.

docker images
Enter fullscreen mode Exit fullscreen mode
REPOSITORY                   TAG       IMAGE ID       CREATED         SIZE

ajcwebdev/ajcwebdev-docker   latest    cf27411146f2   4 minutes ago   118MB
Enter fullscreen mode Exit fullscreen mode

4. Run the image

Docker runs processes in isolated containers. A container is a process which runs on a host. The host may be local or remote.

Run the Docker container with docker run

When an operator executes docker run, the container process that runs is isolated in that it has its own file system, its own networking, and its own isolated process tree separate from the host.

docker run -p 49160:8080 -d ajcwebdev/ajcwebdev-docker
Enter fullscreen mode Exit fullscreen mode

-d runs the container in detached mode, leaving the container running in the background. The -p flag redirects a public port to a private port inside the container.

List containers with docker ps

To test your app, get the port of your app that Docker mapped:

docker ps
Enter fullscreen mode Exit fullscreen mode
CONTAINER ID   IMAGE                        COMMAND                  CREATED          STATUS          PORTS                                         NAMES

d454a8aacc28   ajcwebdev/ajcwebdev-docker   "docker-entrypoint.s…"   13 seconds ago   Up 11 seconds   0.0.0.0:49160->8080/tcp, :::49160->8080/tcp   sad_kepler
Enter fullscreen mode Exit fullscreen mode

Print the output of your app with docker logs

docker logs <container id>
Enter fullscreen mode Exit fullscreen mode
Running on http://0.0.0.0:8080
Enter fullscreen mode Exit fullscreen mode

Docker mapped the 8080 port inside of the container to the port 49160 on your machine.

Call your app using curl

curl -i localhost:49160
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 200 OK

X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 25
ETag: W/"19-iWXWa+Uq4/gL522tm8qTMfqHQN0"
Date: Fri, 16 Jul 2021 18:48:54 GMT
Connection: keep-alive
Keep-Alive: timeout=5

<h2>ajcwebdev-docker</h2>
Enter fullscreen mode Exit fullscreen mode

02-localhost-49160

5. Create Docker Compose file

Compose is a tool for defining and running multi-container Docker applications. After configuring your application’s services with a YAML file, you can create and start all your services with a single command.

touch docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Define the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.

version: "3.9"
services:
  web:
    build: .
    ports:
      - "49160:8080"
Enter fullscreen mode Exit fullscreen mode

Create and start containers with docker compose up

Stop your currently running container before running the next command or the port will be in use.

docker stop <container id>
Enter fullscreen mode Exit fullscreen mode

The docker compose up command aggregates the output of each container. It builds, (re)creates, starts, and attaches to containers for a service.

docker compose up
Enter fullscreen mode Exit fullscreen mode
Attaching to web_1
web_1  | Running on http://0.0.0.0:8080
Enter fullscreen mode Exit fullscreen mode

6. Push your project to a GitHub repository

We can publish this image to the GitHub Container Registry with GitHub Packages. This will require pushing our project to a GitHub repository.

Create a new blank repository

You can create a blank repository by visiting repo.new.

Create .gitignore file

touch .gitignore
Enter fullscreen mode Exit fullscreen mode

Add the following files to .gitignore.

node_modules
package-lock.json
yarn.lock
.DS_Store
.env
dist
build
*.local
Enter fullscreen mode Exit fullscreen mode

Initialize Git and push to newly created repo

git init
git add .
git commit -m "I can barely contain my excitement"
git branch -M main
git remote add origin https://github.com/ajcwebdev/ajcwebdev-docker.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

7. Publish to GitHub Container Registry

GitHub Packages is a platform for hosting and managing packages that combines your source code and packages in one place including containers and other dependencies. You can integrate GitHub Packages with GitHub APIs, GitHub Actions, and webhooks to create an end-to-end DevOps workflow that includes your code, CI, and deployment solutions.

GitHub Packages offers different package registries for commonly used package managers, such as npm, RubyGems, Maven, Gradle, and Docker. GitHub's Container registry is optimized for containers and supports Docker and OCI images.

Login to ghcr.io with docker login

To login, create a PAT (personal access token) with the ability to read, write, and delete packages and include it instead of xxxx.

export CR_PAT=xxxx
Enter fullscreen mode Exit fullscreen mode

Login with your own username in place of ajcwebdev.

echo $CR_PAT | docker login ghcr.io -u ajcwebdev --password-stdin
Enter fullscreen mode Exit fullscreen mode

Tag image

docker tag ajcwebdev/ajcwebdev-docker ghcr.io/ajcwebdev/ajcwebdev-docker
Enter fullscreen mode Exit fullscreen mode

Push to registry

docker push ghcr.io/ajcwebdev/ajcwebdev-docker:latest
Enter fullscreen mode Exit fullscreen mode

Pull your image

To test that our project has a docker image published to a public registry, pull it from your local development environment.

docker pull ghcr.io/ajcwebdev/ajcwebdev-docker
Enter fullscreen mode Exit fullscreen mode
Using default tag: latest
latest: Pulling from ajcwebdev/ajcwebdev-docker
Digest: sha256:3b624dcaf8c7346b66af02e9c31defc992a546d82958cb067fb6037e867a51e3
Status: Image is up to date for ghcr.io/ajcwebdev/ajcwebdev-docker:latest
ghcr.io/ajcwebdev/ajcwebdev-docker:latest
Enter fullscreen mode Exit fullscreen mode

You can view this published container on my GitHub.

Discussion (2)

Collapse
nheindev profile image
Noah Hein

I'm all aboard for more Docker content! Glad to see you exploring this side of the interwebs.

Collapse
ajcwebdev profile image
anthony-campolo Author

Yeah, I've started checking out some non-serverless providers (what was once known as "running a server") and it seems like they're not really any simpler than just running your own containers on AWS or something.

Still early days so my initial perspective might change. Or we might see some incredible containers-as-a-service companies start to pop up all over the place.