General Middleware

Learn Docker from Scratch

What is Docker and Container?

In order to deploy applications and solutions in production environment, the traditional way is to install the products in VMs (Virtual Machines), but there are certain disadvantages to this approach.

  1. Each VM occupies considerable storage space due to duplication of operating system and other code.
  2. Since the setup in non-production may vary from that in production, due to a lot of manual intervention, identifying issues in production sometimes becomes challenging.
  3. It involves a lot of duplication of code.
  4. Scaling up due to increased workload is a considerable effort.

Docker evolved as a solution to all the above problems. Docker is an engine running on top of any operating system and it helps to run code in the form of containers, which are self-contained entities, where all dependencies are integrated, and thus able to run by itself with no dependency on the host operating system. It brings along the below advantages:

  1. Avoids duplication
  2. Same code an be run on any host operating system
  3. Can be scaled up and down very easily
  4. Promotion of code from dev to prod doesn’t have any dependency on the host operating system

Images vs Containers

1. Images are the templates based on which, the individual containers (self-contained processes) are spawned1. Containers are the running instances that gets swooned, based on images
2. Images are static entities2. Containers are the running processes, hence dynamic in nature

Docker Installation

Docker Installation is straight forward. Follow the documentation to install docker in your preferred platform.

Build an Image from Dockerfile

docker build -t <imagename:tag> .

Example : docker build -t node server:v1 .

Note : The Dockerfile needs to be in the same directory where you execute the docker command.

Example of Dockerfile

FROM node
COPY . /apps
RUN ["npm", "install"]
CMD ["node", "app.js"]

List images after build

docker images

Note : “docker images” is equivalent to “docker image ls”

Remove all unused images

docker image prune

Remove selected images

docker image rm <list of images separated by comma>

Spawn container from an image

docker run -itd <docker_image>

Salient points on Containers

  1. In case you want to interact with a container, while spawning, you need to use the interactive option (-it) with the “docker run” command.
  2. If you don’t use the interactive option, and there is no active running CMD, defined in the Dockerfile of the image, the container exists after getting spawned.
  3. To keep the container running, there has to be a running command, defined in the CMD or ENTRYPOINT section of the Dockerfile of the image.
  4. “docker run <image>” will, by default, attach to the container, and you can only exit from the prompt from another terminal.
  5. “docker run -d <image>” will spin the container, but detach the current shell from the container
  6. “docker run -it <image>” will spin the container, and attach an interactive shell to it, so that the executor can interact with the container
  7. “docker run -itd <image>” will spin the container, create an interactive shell, and detach the current shell from the running container. Ideally, using -it and -d together does not make sense, since the interactive shell is destroyed by detaching the current shell from the container
  8. A running container creates its files and directories under /var/lib/docker/overlay2/<random_id>/merged
  9. You can get the logs of a running container using either “docker attach <conteiner_id|name>” OR “docker logs <container_id>|name” or “docker logs -f <container_id>|name”

Interact with a container

docker exec -it <container_id> /bin/bash

Look into the contents of a docker image

docker inspect <image_name>

Note: Docker images have tags (-t) and IDs, whereas docker containers have Names and IDs.

Docker Registry

You can build a docker image and push it to the public registry or any private registry. Below are the precise steps for the same.

1. Create a Dockerfile

Example Dockerfile
-------------------------
FROM tomcat
ENTRYPOINT ["/usr/local/tomcat/bin/startup.sh"]

2. Build an image with the Dockerfile with the right name. You need to use the name as <dockerhub account>/<your repository in dockerhub>:<image tag>

docker build . -t anbanerj11/tomcat_animesh:v1

Note that if you have build the image already with the different name, you can retag the image using below command.

docker tag tomcat_animesh:v1 anbanerj11/tomcat_animesh:v1

In addition, the target registry is dockerhub, by default. In case you want to connect to a private registry, use the below command.

docker build . -t <registry host>/<registry name>/<account>/tomcat_animesh:v1
For docker hub, the registry host is hub.docker.com and the registry name is library.

3. Push the image to dockerhub

docker push anbanerj11/tomcat_animesh:v1

4. Now spawn the container

docker run --name mytomcat anbanerj11/tomcat_animesh:v1

Note that you specify the name of the container using –name flag. If no name is provided, a default name matching with the image is allocated.

Troubleshoot container exit issues

In many situations, containers exit before going into the running state. Let me jot down few important points that helps to both create the right Dockerfile to avoid container failures built from that image spawned from that Dockerfile.

1. There must be either one CMD or ENTRYPOINT instruction in the Dockerfile. Without that, you can’t run a container from that image. Note that the CMD or ENTRYPOINT can be in any layer though. For example, in case you have a FROM instruction in the Dockerfile, and the reference image has an ENTRYPOINT or CMD, that should be fine as well.

Pull an image from registry

docker pull <image_name>
Example : docker pull node

List all the images

docker images

Get all running containers

docker ps

Get all running and not running containers

docker ps -a

Start and Stop a docker container

docker start <container_name>|<ID>
docker stop <container_name>|<ID>

Create a container from a docker image

docker run -d --name <container_name> <image_name>

Remove an already killed container

docker rm <container_id>

Remove all killed containers

docker container prune

Attach to a running container

docker attach <container_name>|<id>

Run a container and remove it from the process list once stopped

docker run -itd --rm tomcat

Dockerfile Commands and Syntax

FROM <image>Creates a layer with the specified image. A Dockerfile starts with this command, which, in most cases, creates the OS layer. However, it can include other images, which includes the OS layer already.
WORKDIR <Path>Change directory inside the container
CMD [“command”,”parameter”]Command to execute while creating a container from an image. There can be only one effective CMD in a Dockerfile. You can specify multiple, but only the last one will take effect. In addition, the CMD instructions can be overridden while running the container by passing the commands in argument.
RUN <command>This is a command to be executed at build time
ENTRYPOINT [“command”]This is executed at runtime, when the ENTRYPOINT instructions come first, followed by the effect CMD instruction, to build the final instructions at container runtime. For example, consider the below instructions in Dockerfile.

CMD [“status”]
CMD [ “run” ]
ENTRYPOINT [ “./catalina.sh” ]

Here is the final command that is executed at runtime: ./catalina.sh run
VARThis is used to define a variable that is known to the container at build time
ENVThis is used to define a variable that is known to the container at build time and at run time.
COPYThis is to copy files and directory contents from local to the container image

How can you build a tomcat image to provide port number as an environment variable

Look at the below Dockerfile. See, how the environment variable PORT is used in another environment variable JAVA_OPTS.

FROM tomcat AS stage
FROM openjdk:11
RUN mkdir /usr/local/tomcat
COPY --chown=root:root --from=stage /usr/local/tomcat /usr/local/tomcat
ENV PORT=8555
ENV JAVA_OPTS="-Dtomcatport=${PORT}"
WORKDIR /usr/local/tomcat/conf
COPY --chown=root:root ./server.xml .
WORKDIR /usr/local/tomcat/bin
CMD [ "run" ]
ENTRYPOINT [ "./catalina.sh" ]

Finally, look at how the tomcat uses the port in server.xml

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">
    <Connector port="${tomcatport}" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

Docker Volumes

Volumes are a way to persist your data inside the container, so that it persists across container restarts or crashes or deletions. Volumes are certain paths in the docker host which are mapped to a certain path inside the container. There are two kinds of Docker Volumes.

1. Bind Mount

docker run --mount type=bind,source=/tmp/temporary,target=/var/ tomcat

In this case, the docker host path /tmp/temporary is bound to the container path /var

2. Volume Mount

docker run --mount type=volume,source=<volume_name>,target=<target_path> <container_name>
Example -
docker run --mount type=volume,source=newvol,target=/tmp/animesh tomcat

Leave a Reply

Your email address will not be published. Required fields are marked *