Docker build!

Dockerfile instructions

Docker builds images automatically by reading the instructions from a Dockerfile -- a text file that contains all commands, in order, needed to build a given image.

A Docker image consists of read-only layers each of which represents a Dockerfile instruction.

The layers are stacked and each one is a delta of the changes from the previous layer.

Each instruction creates one layer:

  • FROM creates a layer from the ubuntu:18.04 Docker image

  • COPY adds files from your Docker client’s current directory.

  • RUN builds your application with make.

  • CMD specifies what command to run within the container.

When you run an image and generate a container, you add a new writable layer (the “container layer”) on top of the underlying layers.

All changes made to the running container, such as writing new files, modifying existing files, and deleting files, are written to this thin writable container layer.

Sample Application

Let’s starts with a simple Node.js application

Dockerfile

Dockerfile
FROM node:14-alpine

WORKDIR /code

COPY package.json /code/package.json (1)

RUN npm install \ (2)
 && npm install -g nodemon@1.11.0 \ (3)
 && npm cache clean --force;  (4)

COPY app.js /code (5)
COPY index.html /code (5)

CMD ["npm", "start"]  (6)
1 Copy package.json file
2 Install the dependencies in the local node_modules folder.
3 Install global
4 Clean cache
5 Copy NodeJS application
6 Run application

Dockerfile instructions

FROM

The FROM instruction initializes a new build stage and sets the Base Image for subsequent instructions.

As such, a valid Dockerfile must start with a FROM instruction. The image can be any valid image

We recommend the Alpine image as it is tightly controlled and small in size (currently under 5 MB), while still being a full Linux distribution.

LABEL

The LABEL instruction adds metadata to an image. A LABEL is a key-value pair. To include spaces within a LABEL value, use quotes and backslashes as you would in command-line parsing.

A example:

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

RUN

RUN has 2 forms:

  • RUN <command> (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)

  • RUN ["executable", "param1", "param2"] (exec form)

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.

Split long or complex RUN statements on multiple lines separated with backslashes to make your Dockerfile more readable, understandable, and maintainable.

For example:

RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo  \
    && rm -rf /var/lib/apt/lists/*

CMD

The main purpose of a CMD is to provide defaults for an executing container.

The CMD instruction has three forms:

  • CMD ["executable","param1","param2"] (exec form, this is the preferred form)

  • CMD ["param1","param2"] (as default parameters to ENTRYPOINT)

  • CMD command param1 param2 (shell form)

There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.

EXPOSE

The EXPOSE instruction indicates the ports on which a container listens for connections.

EXPOSE 8000
The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified.

ENV

To make new software easier to run, you can use ENV

For example

ENV PATH=/usr/local/nginx/bin:$PATH

ensures that CMD ["nginx"] just works.

ENV PG_MAJOR=9.3
ENV PG_VERSION=9.3.4
RUN curl -SL https://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD or COPY

Although ADD and COPY are functionally similar.

  • COPY copies a file/directory from your host to your image.

  • ADD copies a file/directory from your host to your image, but can also fetch remote URLs, extract TAR files, etc…​

  • Use COPY for simply copying files and/or directories into the build context.

  • Use ADD for downloading remote resources, extracting TAR files, etc..

COPY

COPY has two forms:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

e.g.

COPY home /mydir/
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/

The COPY instruction copies new files or directories from <src> and adds them to the filesystem of the container at the path <dest>.

ADD

ADD has two forms:

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

e.g.

ADD home /mydir/
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

The ADD instruction copies new files, directories or remote file URLs from <src> and adds them to the filesystem of the image at the path <dest>

ENTRYPOINT

ENTRYPOINT has two forms:

The exec form, which is the preferred form:

ENTRYPOINT ["executable", "param1", "param2"]

The shell form:

ENTRYPOINT command param1 param2

An ENTRYPOINT allows you to configure a container that will run as an executable.

docker run -i -t --rm -p 80:80 nginx

Let’s see with an example of an image for the command line tool s3cmd:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

Now the image can be run like this to show the command’s help:

docker run s3cmd

USER

The USER instruction sets the user name (or UID) and optionally the user group (or GID) to use when running the image and for any RUN, CMD and ENTRYPOINT instructions that follow it in the Dockerfile.

If a service can run without privileges, use USER to change to a non-root user.

FROM alpine
USER swarmlab

WORKDIR

The WORKDIR instruction sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile.

If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction.

For clarity and reliability, you should always use absolute paths for your WORKDIR

package.json

package.json
{
    "main": "app.js",
    "dependencies": {
        "express": "~4.14.0",
        "express-handlebars": "~3.0.0"
    }
}

A package.json file:

  • lists the packages your project depends on

  • specifies versions of a package that your project can use using semantic versioning rules

  • makes your build reproducible, and therefore easier to share with other developers

app.js

app.js
var express = require('express');
var expressHandlebars = require('express-handlebars');
var http = require('http');

var PORT = 8000;

var LINES = [
        "Ποιος μας γηροκομεί  τη σήμερον ημέρα, ψηστιέρα, καρβουνιέρα  μούσα δεκεμβριανή.",
        "Πολέμησα καιρό  σε όλα τα πεδία  και με τυφλή μανία  ξέσκιζα τον εχθρό.",
        "Τώρα με χειρουργεί  η αλλήθωρη νεολαία, μια τσογλανοπαρέα, που κάνει κριτική.",
];

var lineIndex = 0;

var app = express();
app.engine('html', expressHandlebars());
app.set('view engine', 'html');
app.set('views', __dirname);
app.get('/', function(req, res) {
    var message = LINES[lineIndex];

    lineIndex += 1;
    if (lineIndex >= LINES.length) {
        lineIndex = 0;
    }

    res.render('index', {message: message});
});

http.Server(app).listen(PORT, function() {
    console.log("HTTP server listening on port %s", PORT);
});

index.html

index.html
<html>
    <head>
        <meta http-equiv="refresh" content="2">

        <style type="text/css">
            body {
                font-family: Helvetica, Arial, sans-serif;
                font-weight: 600;
                font-size: 56pt;
                text-transform: uppercase;
                text-align: center;
                background: #3c3;
                color: white;
            }
        </style>
    </head>

    <body>&ldquo;{{message}}&rdquo;</body>
</html>

Docker build

Build an image from a Dockerfile

docker build [OPTIONS] PATH | URL | -

Create files

  • Dockerfile

  • app.js

  • package.json

  • index.html

docker build
docker build -f Dockerfile -t mynodejs .

Start

Now that we have an image, let’s run the application! To do so, we will use the docker run command

docker run -it -p 8000:8000 mynodejs /bin/sh -c "node app.js"
Ctrl + C tells the program that you want to interrupt
http://localhost:8000

RUN URL in a web browser