Containers & Docker
Docker is a platform for building, running and deploying applications in a consistent manner.
We can use Docker to create a deployment container that contains the complete runtime environment, which can then be run anywhere that has Docker installed - a different machine, or even on a cloud service!
No more issues of “it works on my computer”… with a container it can run anywhere!
Virtual Machine: an abstraction of a physical machine, each of which has its own resources and dependencies.
- Each virtual machine is running a complete OS
- Resource intensive, since each VM is allocated its own memory, CPU cycles etc.
Container: an isolated environment for running an application.
- Run applications (not OS) in isolation.
- Containers are processes that use the OS of the host to run an
image
containing the application/ - Very lightweight, fast to startup containers vs a VM.
– Image Credit: Mosh Hamedani. https://www.youtube.com/watch?v=pTFZFxd4hOI
Advantages of Containers
- Containers are significantly smaller than virtual machines, and use fewer hardware resources.
- You can deploy Docker containers anywhere, on any physical and virtual machines and even on the cloud.
- Docker containers are pretty lightweight, and are very easily scalable.
Download and install directly from the Docker website, or your favorite package manager. Make sure to install the correct version for your system architecture (I’m looking at you, Apple ARM).
Check that it’s installed and available on your path.
$ docker version
Client: Docker Engine - Community
Version: 20.10.21
API version: 1.41
Go version: go1.19.3
Git commit: baeda1f82a
Built: Tue Oct 25 17:53:02 2022
OS/Arch: darwin/arm64
Context: default
Experimental: true
Server: Docker Desktop 4.15.0 (93002)
Engine:
Version: 20.10.21
API version: 1.41 (minimum version 1.12)
Go version: go1.18.7
Git commit: 3056208
Built: Tue Oct 25 17:59:41 2022
OS/Arch: linux/arm64
Experimental: false
containerd:
Version: 1.6.10
GitCommit: 770bd0108c32f3fb5c73ae1264f7e503fe7b2661
runc:
Version: 1.1.4
GitCommit: v1.1.4-0-g5fd4c4d
docker-init:
Version: 0.19.0
GitCommit: de40ad0
- Create an image, which includes both your application and a configuration file describing how to run it.
- Tell Docker to run this image in a container, if you wish to run it locally.
- Upload the image to the Docker registry, which allows someone else to download and run it on a different system.
A docker
image
contains everything that is needed to run an application
- a cut-down OS
- a runtime environment e.g. jvm
- application files
- third-party libraries
- environment variables
Let’s build a simple application, and then turn it into a Docker image. e.g.
fun main() {
println("Hello Docker!")
}
$ kotlinc Hello.kt -include-runtime -d Hello.jar
$ java -jar Hello.jar
Hello Docker!
To bundle this application, create a Dockerfile
i.e. a configuration file for your image that describes how to execute it.
# Dockerfile
FROM eclipse-temurin:17
COPY Hello.jar /app
WORKDIR /app
CMD java -jar Hello.jar
You can find suitable Docker images on https://hub.docker.com. In this case, we’re using Temurin JDK as our base image (Linux/Java installation).
To create the image:
$ docker build -t hello-docker .
To see the image that we’ve created:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-docker latest a615e715b56d 7 seconds ago 455MB
To run our image:
$ docker run hello-docker
Hello Docker!
Keep in mind that you are publishing your directory contents, and then running the jar file that you packaged. Docker doesn’t recompile or rebuild anything! If you make changes to your source code, remember to recompile and rebuild the jar file, otherwise those changes won’t show up in your image.
To make this image available to other systems, you can publish it to the Docker Hub, and make it available to download. See Docker repos documentation for more details.
- Create an account on Docker Hub if you haven’t already. Login.
- Create a repository to hold your images.
- Tag your local image with your username/repository.
- Push your local image to that repository.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-docker latest f81c65fd07d3 3 minutes ago 455MB
$ docker tag f81c65fd07d3 jfavery/cs346
$ docker push jfavery/cs346:latest
The push refers to repository [docker.io/jfavery/cs346]
5f70bf18a086: Pushed
8768f51fa877: Pushed
5667ad7a3f9d: Pushed
6ea5779e9620: Pushed
fb4f3c9f2631: Pushed
12dae9600498: Pushed
latest: digest: sha256:6ddd868abde318f67fa50e372a47d4a04147d29722c4cd2a59c45b97a413ea22 size: 1578
To pull (download) this image to a new machine, use docker pull
.
$ docker pull jfavery/cs346
Using default tag: latest
latest: Pulling from jfavery/cs346
0509fae36eb0: Pull complete
6a8d9c230ad7: Pull complete
0dffb0eed171: Pull complete
77de63931da8: Pull complete
dc36babb139f: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:6ddd868abde318f67fa50e372a47d4a04147d29722c4cd2a59c45b97a413ea22
Status: Downloaded newer image for jfavery/cs346:latest
docker.io/jfavery/cs346:latest
$ docker run hello-docker
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jfavery/cs346 latest f81c65fd07d3 10 minutes ago 455MB
$ docker run jfavery/cs346
Hello Docker!
To run a long-running program (that doesn’t halt after execution), use the -d
flag.
$ docker run -d jfavery/cs346
So what is happening is that when we launch a container, it creates a new environment from the image and sets up the container with its own mutable environment and data. This works great, until you stop the container - when you restart it, the environment is recreated and you lose any previous data!
How do we avoid this? We can create a volume
on the host OS, outside of the scope of the container, and then provide the container access to the volume. For example, we can create a data file that will persist after container restarts.
# create a volume on the host
$ docker volume create data-storage
# attach the volume to the container when you launch it
$ docker run -v data-storage:/etc/todos jfavery/cs346