How to Write Dockerfile and Basic Docker Commands
Tags: docker, dockerfile
Published: 2023-01-23 20:54:31

Seneca OSD700 team project `Starchart` will use many `Docker` containers. I would like to summarize how to write `Dockerfile` and basic `Docker` commands. 

First we need to write a `Dockerfile` which is a set of instruction to build a `Docker` image. A `Docker` image is a read-only template with instructions for creating a Docker container and the container is a runnable instance of an image. It's probably better to understand with below `node` project example. 

```Dockerfile
FROM node:16.15.1
LABEL maintainer="Wonkeun No <wonkeun.no@gmail.com>"
ENV PORT=8080
WORKDIR /app
COPY package.json ./
RUN npm install
COPY ./src ./src
CMD npm start
EXPOSE 8080
```

`Dockerfile` must begin with `FROM` to specify what parent image the container uses. In this case, I use `node` version `16.15.1`. So my app will be running on top of `node` image which someone built for everybody. 

`LABEL` is not a mandatory command, but you can add some metadata information about the image as a `key=value` pair. You can add more like `version`, `description`, etc. 

Environment variable can be set with `ENV` command. It can be overridden with `docker` command. 

`WORKDIR` sets the working directory. Multiple `WORKDIR` is also possible. When a relative path is provided, it will be relative to previous `WORKDIR`. 

Then, copy `package.json` and paste it to `./` directory  with `COPY` instruction. And run `npm install` with `RUN` to download npm dependencies in this case. 

Finally copy all `./src` files and paste into container's `./src` directory and run `npm start` with `CMD` with port 8080 open outside of container. You can run many `RUN` instructions, but `CMD` can be run once as a container launch command. You can still write multiple `CMD`, but all will be ignored by last `CMD`. 

Once `Dockerfile` is written, it's highly recommended to ignore some of files that are not necessary in `docker build` by adding them to `.dockerignore`. This process helps to reduce the size of image and build faster. You secret information files should not be in the image either. 

There is more information about `Dockerfile` in the official `Docker` site. [Docker reference](https://docs.docker.com/engine/reference/builder)

Now it's ready to build the image with below command.

```sh
docker build -t <image>:latest .
```

`-t` can specify a tag for the image. This option can be combined with `-i` (i.e. shortened form of `--interactive`) to interact with container in a terminal. It's useful to see what is going on inside the container.  

If the build is successful, you can find the image with below command. 

```sh
docker image ls 
```

Now it's time to actually run the container. 
```sh
docker run --rm --name <image> --env-file .env -e LOG_LEVEL=debug -p 8080:8080 <image>:latest -d
```

Here's explanation for each option.
- `--rm` removes the container when it exits automatically
- `--name` assign a name to the container
- `--env-file` specifies a file that defines environment variables
- `-e` sets an environment variable for the container
- `-p` binds the host machine's port with container's port (i.e. [HOST_MACHINE_PORT:CONTAINER_PORT]
- `-d` runs the container in background and print container ID

Once you run above command, you will see the container's ID. Run below to see the logs from the container. `-f` option is to continuously print out the logs as it generates. 

```sh
docker logs -f <container_id>
```

This article helps to create a `Docker` image and run a single container. I will write more about best practises of writing `Dockerfile` , `DockerHub`, and `docker-compose`.