-
Notifications
You must be signed in to change notification settings - Fork 11
WorkBook
Publication Date: 01-Nov-2023
You can find up to date versions of this workbook and supporting material at https://github.com/dockerfornovices/DockerSimpleDemo
A copy of this workbook is also available on Google Drive. Feel free to make a copy and add your own notes.
https://drive.google.com/file/d/1LzB3hn4OecuNX0cJWKQ0nxKwkSxWOdJL/view?usp=sharing
All of the Docker images used in this workbook have been designed to be as small as possible so that they will be useful on congested or slow networks.
This workbook is written for people attending the related “Docker for Novices” workshop. It may be useful as a stand-alone resource, but it will be useful to review the slides as well.
You will need a working Docker environment. Follow the instructions in the SetUp section.
NOTE: These examples do not show all the options or uses of Docker, and the explanations are not exhaustive. You should have the Docker documentation handy as you try to understand what is happening. For example https://docs.docker.com/engine/reference/commandline/container/ and https://docs.docker.com/engine/reference/commandline/image/
All these examples use the Docker command line interface and you will
need to open a terminal with access to the docker command. Some
tasks will require two terminals to be open.
Before starting the workbook download the files from the companion Git repo (you can clone the repo or download a zip version).
Command to run:
docker image pull alpine:latest
This will download the official image for Alpine
Linux from Docker
Hub, specifically the version tagged with
the string “latest”.
Notes:
- Docker Hub is a public registry of many, many, images you can download and use.
- When pulling images from the command line, it’s common to omit the
tag and assume the default
latest. However when specifying images for deployment (e.g. in a Docker file) then the version tag should always be present. - The tag
latestis not guaranteed to be the latest version. It’s just the default tag, and by convention it is usually the latest production version of a container image. But this is not a guarantee and you should check that the latest really is the correct version for your purposes, or specify a different tag. - In order to save people typing effort when doing these tasks we will
often omit the tag and assume the default latest. That also means
that in the later build exercises we use the
latesttag to reduce image downloads.
Command to run:
docker image inspect alpine|less
There is a lot of information, so it can be confusing, but as an example
look for the values for Cmd and Env metadata.
(Remember the search command in less is the / character.)
Note: Passing value via the environment (Env metadata) is one of the ways we pass config information into a container at start-up. There are multiple values for Cmd and Env because of the way that images are build up from layers. The commands below resolve this ambiguity.
Some handy versions of the inspect command
docker image inspect alpine --format '{{.ContainerConfig.Cmd}}'
docker image inspect alpine --format '{{.ContainerConfig.Env}}'
More on the inspect command later on.
Command to run:
docker container run -it --name myAlpine alpine
The -it options (interactive and terminal) connect the container to
your terminal session. Refer to the
documentation
for more details.
You can now type Linux shell commands at the container shell prompt. For
instance get a list of every process running in the container with
ps -ef (it’s a very short list!)
Type exit to stop the container and then type
docker container rm myAlpine to remove the container.
Note: Many Linux images for Docker, by default, do not contain the usual full featured shell program you are probably used to (for example Bash or Zsh). Instead a smaller program such as Dash or BusyBox is installed. For many environments this should not be a problem, but be aware that your favourite advanced shell features may not the available “out of the box”. You can always build an image with your favourite tools installed – more in building images later.
TIP: Alpine Linux is a very small and useful Linux distro. However if it
is too limited for your needs and you want to use Debian (for example)
then consider using a *slim tag. For instance the
debian:testing-slim image is approximately 80Mb, compared to 124Mb
for debian:testing (at the time of writing).
Note: This task should be skipped if short of time or you have limited network access.
-
Delete the image we downloaded
docker image rm -f alpine:latest
(the
-fforces the image to be removed, which means any related containers are shut down first if needed, use with care) -
Now try and run the image again
docker run -it --name myAlpine alpine
If the image is not in our local cache then it will get automatically
downloaded, in simple development environments we don’t often use
docker image pull ... explicitly.
This container has been given a name with --name myAlpine so that we
can refer to it in the net task.
Note: We will look at the life cycles of containers in the next section.
For now let’s just shut down the running container called myAlpine.
-
Stop the container with
docker container stop myAlpine
-
The container still exists and could be restarted if needed. You can see this with
docker container ls -a
or
docker container ps -a
-
Remove the container with
docker container rm myAlpine
It’s tedious to keep typing docker container ... and
docker image ... so let’s make life easier with some aliases.
alias doci="docker image" alias docc="docker container"
In PowerShell you can create functions
Function docc {&"docker.exe" container $Args}
Function doci {&"docker.exe" image $Args}
PowerShell Notes
-
When using these functions it’s important to quote any docker arguments that contain commas “
,”. This stops PowerShell from turning them into an array. For example:docc run --mount "type=bind,source=$PWD\SQLite,destination=/code" --rm -it ...
-
These examples were tested using PowerShell 7.x
The rest of these examples assume you have created these aliases.
-
Tidy up from last task, just in case.
docc rm -f myApline
Note: Use the
-f(force) option with care because it also shuts down any related containers that might be running (or stopped). -
Let’s see what’s running
docc ls -a
should be an empty list (more on the
-aoption in a minute) -
Start a container with
docc run -d --name myAlpine alpine /bin/sh -c 'while echo $(( i += 1)) ; do sleep 5 ; done'
This image will keep running in detached mode, printing out integers to stdout every five seconds. You can verify this by looking at the container log with the command
docc logs --follow myAlpine
Hit
ctrl-Cwhen you get bored to exit the log display.
What is the state of the container?
-
Run the commands
docc inspect --format '{{.State.Status}}' myAlpine docc ls
In both cases the state should be running or up
-
Stop the container with
docc stop myAlpine
This may take a few seconds…
-
What is the state? Run the two commands (
inspectandls) we ran previously. We get different results -
Try the
-aoption ondocc ls, i.e.docc ls -aand we can see the stopped container.
So generally always use the -a option to ls,
i.e. docc ls -a.
Why doesn’t the container just vanish when we stop it? There are a few useful things we can do with a stopped container:
- Access the contents of the container’s modified file system. For
example see the
docker container exportcommand. - Look at the logs
docc logs ... - Restart the container with
docc start ...
Let’s try some of those things on our stopped container
docc start -ia myAlpine
This time we used the -ia options to attach the container to our
terminal and the numbers start appearing this time, starting from one
again. This leads to a couple of observations
- The
docker container logscommand provides access to the containers stdout (and stderr) fle streams. - When the container is (re)started the running process is reset back to it’s initial state.
Now switch to second terminal and see the state of the container now
docc inspect --format "{{.State.Status}}" myAlpine
NOTE: Don’t confuse the start command and the restart command
We can even execute a new process in the container. e.g.
docc exec -it myAlpine /bin/ps -ef
or
docc exec -it myAlpine /bin/sh # Type exit when you get bored
NOTE exec only works with a container that is currently running.
TIP: With the exec command you may not need to install sshd in your
images
TIP: After stopping a container with docc stop ... you can (usually)
use docc start ... to make it active again. However if the container
stopped because of a problem then start will probably not work.
Now let’s really get rid of the container
docc rm -f myAlpine
Now docc ls -a and docc inspect myAlpine show the container
really has gone. This includes any changes the container made to the
file system, because container file systems are not persistent.
TIP: One we are happy we don’t need to access to the container after
it’s stopped we can pass the --rm option to docc run .... For
example
docc run -d --rm --name myAlpine alpine
The container has no command to run so it should exit immediately and
the container removed. You can verify this with the inspect and
ls -a commands
Because containers are isolated, and any changes made to the container file system are temporary, Docker needs to provide specific ways to get information into and out of containers at run time. Broadly these are:
- Publishing Network Ports
- Bind Mounts
- Volume Mounts
- Passing environment values when starting a container
- Writing information in the container log
Note the files for this task are located in the subdirectory
APIserver. The files are not needed to complete the task, but are
provided for later reference.
During this workshop we don’t have time to cover Docker networking, but it’s important to have a basic understanding of how to use port mapping.
Port Mapping allows a container to expose a network end point by publishing an IP port on the Docker container to a port on the Docker host. An example should help make this clear
I’ve created a simple Docker image that runs a Go API server.
The API server code responds to API calls on port 8000. So we can get an interesting result by running the API server in a container, on our local workstation, and then hitting the server with HTML GET request on the correct URL. For example
Pull and run the API container as follows
docc run -p 8089:8000 --rm -d --name myAPIserver dockerfornovices/api-server:latest
The Docker engine exposes the API on port 8089 on the host interface.
The -p option maps the host port 8089 to container port
8000. (use IP address 0.0.0.0. which is every network interface on the
system).
Generally when we are mapping a container resource (e.g. a tcp port
using -p, or a storage volume using -v) to an external entity
the external description is on the left, then a : followed by the
container resource.
Test this by running curl or your browser against the URL
http://0.0.0.0:8089/people. For example with curl
curl -v http://0.0.0.0:8089/people
(Note: Depending on your Docker environment you may need to use
curl -v http://127.0.0.1:8089/people ```) instead You can also test the API with a browser on the same URL. Note that the Dockerfile for this image uses the [`EXPOSE`](https://github.com/dockerfornovices/DockerSimpleDemo/blob/master/APIserver/Dockerfile#L21) instruction for port 8000 #### Task 9.1. Check the health of a container. Extra Bonus -- skip on 1st reading. Side Note for later consumption: the [Dockerfile](https://github.com/dockerfornovices/DockerSimpleDemo/blob/master/APIserver/Dockerfile) for this example has a couple of more advanced features. 1. [Multi stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) to substantially reduce the final image size 2. A [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) command in the [Dockerfile](https://github.com/dockerfornovices/DockerSimpleDemo/blob/master/APIserver/Dockerfile#L23) to create a container healthcheck. You can see the current health of the container with the `inspect` command. For instance
docc inspect –format ‘{{.State.Health.Status}}’ myAPIserver
Note: The values used for `--interval`, `--timeout` and `--retries` are not typical or recommended for daily use. **NOTE**: After completing task Task 9 make sure to remove you container with `docc rm -f myAPIserver` ### A note about Bind Mounts and Volumes Bind Mounts and Volume Mounts are very similar, but they serve different purposes. They both expose persistent file storage to the container, and are both configured using the `--mount` or `-v` option, so it can be confusing. * [Volumes](https://docs.docker.com/storage/volumes/) are used to provide persistent data storage for containers. For example data files, database storage and configuration files, i.e. any information that needs to survive across container restarts should be stored in a volume. Volumes store data in data volumes, which are Docker "objects" in a similar manner to images and containers. They are managed via Docker commands and the underlying file system information cannot accesed except via a running container. * [Bind mount](https://docs.docker.com/storage/bind-mounts/) allow the container to access files located on the host file system. They are useful for giving containers access to source code and related resources during development. Generally bind mounts should **not** be used in production systems. **Note**: Most historical examples of Bind Mounts and Volumes use the Docker `-v` option, but the `--mount` option is now the preferred option. > Tip: New users should use the --mount syntax. Experienced users may be more familiar with the "-v" or "--volume" syntax, but are encouraged to use " --mount", because research has shown it to be easier to use. > > -- The [The Docker Docs website](https://docs.docker.com/storage/bind-mounts/#choosing-the--v-or---mount-flag) We will use the `--mount` option in the following examples, but you will still frequently see the `-v` (`--volume`) option used. ### Task 10. Bind Mounts A custom [Lua](https://www.lua.org/) development image has been created for you to use in the following tasj. We will look at how this image is build later, at the moment you just need to know that by default this image provides access to Lua 5.3. (If you already understand how Docker images are build, you can find the build files in `bindmounts`.) Note an example code file for this task is located in the subdirectory `SQLite`. If you have downloaded or cloned the demo repo to your local machine then make the repository root your current working directory, these commands will then work without change. So let's use a very simple Lua development environment and start coding. 1. If you are not using the example files, on your work station operating system create a development directory, e.g. `mkdir <src>` (you can call your `src` directory anything you want) 2. Copy some Lua code into your new directory. This has already been done in the supplied example, so if you are in the root of the project repo the following commands should work "as is", otherwise modify to suite. 3. Start your development container (built with a simple Lua development environment) with the `--mount` parameter. If you have a different source directory then change the directory `SQLite` in the `--mount` option
docc run –rm -it –mount type=bind,source=$PWD/SQLite,destination=/code –name myLuadev dockerfornovices/lua-dev:latest ```
NOTE: the source directory name must be an absolute path, not
something that be interpreted as a volume name. Absolute paths begin
with a /, ‘.’ or ‘..’. Using the $PWD environment variable is
often useful for this.
- On your workstation (not in your container) start up your favourite
code editor so you edit the Lua code,
e.g.
code SQLite/example1.lua - Run the the Lua code on the container (
./example1.lua) and see the bug - Fix the bug in your editor, save
- Run the the Lua code on the container (
./example1.lua) again and the bug should be fixed - Type exit at the container shell prompt when finished and verify
(using
docker container ls -a) that the container did remove itself immediately, because we passed the--rmon container start-up.
We just used our favourite native text editor to edit code which we then tested on a custom container image tailored for our specific needs. What a great way to set up a development environment!
NOTE: Depending on time pressure consider completing this task after the workshop is finished.
Volumes are an important and potentially complex topic, this task shows how to create and use named volumes only. More information on volumes and how to manage them at https://docs.docker.com/storage/volumes/.
This task uses Docker volumes to persist the contents of an SQL database. For speed and convenience we are using SQLite, however the principles are identical for all data storage methods.
By default the dockerfornovices/sqlite image will display the output
from SQLite .schema command, but if a string is supplied it will
execute that inside the SQLite command processor instead.
-
First let’s just confirm what happens when we don’t use volumes
- Create a table
docc run --rm -it dockerfornovices/sqlite:0.1 "create table t1 (c1 varchar(20));"
- Insert some data
docc run --rm -it dockerfornovices/sqlite:0.1 "insert into t1 values ('hello');"Oops
We’ve run the container twice, once to create a table and then again to insert data. But the changes are thrown away each time the container is removed, so the SQL insert failed.
So let’s fix that with a named volume. (Note: the
/datadirectory is the data directory set up in the Docker file) -
Let’s check to make sure we don’t have the named db-demo volume hanging around already
docker volume ls
We can use the
docker volume rm db-democommand to remove if needed.DANGER: If you want to remove every volume on your machine try
docker volume rm $(docker volume ls -q) -
Run the image three times (so a different container each time) and access our database stored on a persistent volume.
docc run --rm -it --mount "type=volume,source=db-demo,target=/data" dockerfornovices/sqlite:0.1 "create table t1 (c1 varchar(20));"
docc run --rm -it --mount "type=volume,source=db-demo,target=/data" dockerfornovices/sqlite:0.1 "insert into t1 values ('hello');"docc run --rm -it --mount "type=volume,source=db-demo,target=/data" dockerfornovices/sqlite:0.1 "select * from t1;"
-
And we can see our new volume
db-demowithdocker volume ls
and
docker volume inspect db-demo
(Use the
docker volume rm db-democommand to remove when finished)
For you convenience after the workshop the Dockerfile used to build the
image used in this task is located in the directory sqlite.
Note that the Dockerfile for this image uses the
`VOLUME <https://github.com/dockerfornovices/DockerSimpleDemo/blob/master/sqlite/Dockerfile#L22>`__
instruction for the /data database location.
The files for this task are located in the directory bindmounts.
Next we are going to re-use the image provided for Task 10 that will allow us to test our Lua code under different versions of the Lua interpreter.
The container takes an “argument” at startup in the form of a environment variable that configures the Lua version we need when the container starts. This configuration takes place when the shell starts and process the ENV environment.
… interactive shells expand the ENV variable and commands are read and executed from the file whose name is the expanded value.
– Bash man page
# Setup Lua version. Default to 5.3
THIS_LUA=${LUA_VERSION:-5.3}
mkdir /tmp/bin
PATH=/tmp/bin:$PATH # Make sure our custom links are 1st on PATH
ln -s /usr/bin/luac${THIS_LUA} /tmp/bin/luac
ln -s /usr/bin/lua${THIS_LUA} /tmp/bin/lua
This has been setup for you already. You will learn more about how to do this when we build images later.
-
We can see this by running the image as follows
docc run --rm -it -e "LUA_VERSION=5.2" --name myLuaDev dockerfornovices/lua-dev:latest
Type
lua -vat the container prompt to see the currently active version, and thenexit. -
Now try running the container again with different values for
LUA_VERSION, and also omitting the environment setting completely, to see how the Lua version changes.
In Docker the container’s stdout and stderr streams are treated as the
log and can viewed with the docker container logs ... command.
- Start an image
docc run -d --name myAlpine alpine /bin/sh -c 'while echo $(( i += 1));do sleep 5;done'
This container keep printing out integers every five seconds to the
container standard out (stdout).
But we started the container in detached mode (-d option), so we
can’t see the logs.
But we can see the log output with the docker container logs
command, .e.g.
docc logs --follow myAlpine
Hit <ctrl-c> to exit the log display and then remove the image with
docc rm -f myAlpine
NOTES:
- Because Docker expects containers to write log messages to stdout or stderr. Docker start up scripts or commands (configured by the CMD or ENTRYPOINT entries in the Dockerfile, see next task) should connect and application logs to stdout after starting the main container process.
- Container logs are stored in a circular FIFO buffer. After a while containers will overwrite older log data.
There are lots and lots of Docker images around the Internet. But sometimes you need something different specific to your needs.
Let’s build the image from task Task 12, an image contains multiple versions of Lua.
As well installing the different versions of Lua we will also need to
copy over the custom $ENV script.
The example files for this activity are located in bindmounts.
Create a dockerfile, using the the default name Dockerfile.
FROM alpine:3 #Build this image with "docker image build -f lua.dockerfile --tag lua-dev ." #then run with "docker container run --rm -it --name myLuadev lua-dev" LABEL maintainer "Alec Clews <alecclews@gmail.com>" LABEL description "Linux with Lua, Versions 5.1, 5.3 and 5.3" RUN apk add --no-cache lua5.1 lua5.2 lua5.3 # Setup up file will configure the correct version of Lua COPY lua.setup.sh /lua.setup.sh ENV ENV /lua.setup.sh RUN chmod 755 /lua.setup.sh # Default starup command CMD "/bin/sh" # Add a volume to hold the development code VOLUME "/code" # Make the code directory the default on startup WORKDIR "/code"
Tip: if the defaulltt
This docker file is very simple, but does include the the important commands you will see in most Docker files:
- The
FROMdirective is the base image we are building on. Images are always built in layers on top of a base image. -
LABELinserts information in the image that can be retrieved with thedocker image inspect ...command. These two examples are the very bare minimum expected in an image - The
RUNcommand executes Linux shell commands (e.g. package management). This is how custom content is added to the image -
COPYtransfers files from the build context (the directory listed as the end of thedocker buildcommand) into the image. -
ENTRYPOINT(orCMD) defines the command that will be run (by default) when the container starts. -
VOLUMEdescribes a volume or bind mount that will be configured at run time.
-
Create a new empty directory that will contain only the files needed to build your container. For the workshop a directory has already been provided with the needed files (
bindmounts) -
At run time we want a
$ENVfile that will set up the correct lua version depending on the environment variable$LUA_VERSION.That file needs to be in the
bindmounts(the docker build context) directory along with our Docker file. It can then be copied into our image during the build. -
Make the docker build context the current default directory. If you have a copy of the repository locally then make bindmounts your current default directory.
cd bindmounts
-
Build
docker image build --tag lua-dev:0.1 .
TIP: Notice the build context (
.) at the end of the command. Very important, and very easy to miss!TIP: This also works from the repo root directory as long as we provide the correct build context path
doci build -f bindmounts/lua.dockerfile --tag lua-dev:0.1 bindmounts
More details on the
--tagoption later, but for now just know that it assigned a user friendly “alias” to our new image. -
Use the
docker image inspect ...to verify the value of the labelsmaintaineranddescription -
From the repository root (you might need to run
cd ..) test the image with the LUA source code from our previous bind mount example (SQLite)docker container run --rm -it \ --mount type=bind,source=$PWD/SQLite,target=/code \ -e "LUA_VERSION=5.2" --name myLuaDev lua-dev:0.1
Congratulations! You created a custom Docker image for your specific requirements which you can run whenever you need it.
There are a few things that are not optimal with this build
- It uses the Lua versions available in the Alpine package repo, which may not contain the version one we need
- We will need to add some additional Lua libraries to do any useful work.
- Everything is done under the root account
By default images run processes under the container root account. When using bind mounts to write content the file will belong to user id 0 on the host, which is the root account on the host as well. It’s then very hard to use these files, and it’s also bad security practice.
To fix that we can make the container run under the same user and group
as the current user (--user option).
docker container run --rm -it \ --user=$(id -u):$(id -g) \ --mount type=bind,source=$PWD/SQLite,target=/code \ -e "LUA_VERSION=5.2" --name myLuaDev lua-dev:latest
NOTE: $(id -u) and $(id -g) return the current user id and group
id for the user running Docker on the host. See Command
Substitution(https://www.gnu.org/software/bash/manual/bash.html#index-command-substitution)
However this approach has limitations and it’s often better to create an unprivileged user account in a custom image. Refer to the example provided in the repo.
Browse Docker Hub were you can find lots of pre-build images ready for you to use.
You can search for images that have Lua and look at the Dockerfile used to create the image. For example this image, which is build with this Dockerfile by Nick Muerdter. It build the requested Lua version from source, a common pattern when creating custom images.
Note: If short of time consider skipping this task
Every Docker image can be identified by a sha1 hash, which can be handy,
but is not very user friendly. To see the sha1 ID of an image use the
docker image ls command.
As well as the ID, images can be tagged with “aliases”. The term aliases is not generally used, but the commonly use term tag is misleading as it is also a specific sub field of the alias. Let’s look at an example
- Create a local image and then run ls to see the details
Before starting You can remove any previous builds of these image
doci rm lua-dev:0.1
Now build the image
doci build -f lua.dockerfile --tag lua-dev:0.1 bindmounts
doci ls
and see something like
REPOSITORY TAG IMAGE ID CREATED SIZE lua-dev 0.1 7a1a8dbdbcfd 3 minutes ago 4.7MB
As you can see the alias consists of two fields, the repository name and the tag.
Run a container from the image using either the alias or the ID
docc run --rm -it lua-dev:0.1
docc run --rm -it 7a1a8dbdbcfd
TIP: When using the ID, only enough of the ID string is required to be unique. So this also works. This is often a lot easier than typing in the alias.
docc run --rm -it 7a1
However the second reason for using an alias is to push and pull images to and from Docker registry over the network.
For example I have an account on Docker Hub called dockerfornovices
and I can push my new image to Hub.
Note: In these examples you will need to supply your own Docker Hub account name
- Give the image the correct alias that includes my account name
- Push using the new alias
Images can have multiple aliases so we don’t need to rebuilt. Just
assign an additional alias (with the tag command) to the existing
image
doci tag lua-dev:0.1 dockerfornovices/myimage:0.1
Notice how the in both cases it’s the same image (look at the ID), but an entry shows up for each alias.
REPOSITORY TAG IMAGE ID CREATED SIZE dockerfornovices/myimage 0.1 7a1a8dbdbcfd 17 minutes ago 4.7MB lua-dev 0.1 7a1a8dbdbcfd 17 minutes ago 4.7MB
This image can be pushed to Docker Hub, assuming I already have access to the correct account
At the Docker command line
docker login -u <username> #Enter password when prompted doci push dockerfornovices/myimage:0.1
Docker Hub is not the only registry you can use. You just need to add extra information to the alias to reference different providers (including your own private local registries).
For example you download and run one of my images from the GitLab registry as follows
docker run -it --rm --name mySQLite registry.gitlab.com/alecthegeek/dockerimages/sqlite:0.1 $'create table t1 (c1 varchar(20));'
There can be variations on the way that different registries specify the name space containing the Docker image, but the basic layout is as follows:
<host-name><:port-number></namespace>/<repo-name>:<tag>
Everything but the repo-name will default to the following values:
-
host-name: Docker Hub -
port-number: Docker default port -
namespace: blank – for any images you need to push you will need to supply a value here. For example your Hub user account. When pulling an image a blank namespace will pull from the Hub default repositories -
repo-name: Must be supplied -
tag: “latest”
TIP: GitLab provides Docker private registry services free of cost. See https://docs.gitlab.com/ee/user/project/container_registry.html
Side Note: It is possible, but not common, to transfer Docker images
using Sneakernet as
explained
here
(basically use the docker image save ... and
docker image load ... commands).
Note: If short of time consider skipping this task
The files for this task are located in the directory buildargs for
later reference.
In task Task 12 we looked at how to use environment values in a running
container via the -e option.
It’s also possible to pass configuration values to an image at build time. Let’s modify our previous Lua development environment. Instead of passing in the required Lua version at run time, we’ll pass it in at build time and create an image with only the single required version of Lua.
In the example Docker file, luadev.dockerfile, the value of the
build argument LUA_VERSION is used when installing Lua. A default
value is also supplied if no value is supplied during the build.
Note: We can then reference this as am ARG value in the dockerfile.
-
Build a new image passing in a Lua version with the
--build-argoptiondocker image build -f buildargs/lua.dockerfile --build-arg LUA_VERSION=5.2 --tag lua-dev:local-5.2 buildargs
or make sure
buildargsis the current working directory and rundocker image build -f lua.dockerfile --build-arg LUA_VERSION=5.2 --tag lua-dev:local-5.2 .
Note the
--tagoption is used to identify the image, compared to otherlua-devimages we may have -
Verify that the correct version has been installed with
docker container run --rm -it --name myLuadev lua-dev:local-5.2 lua -v
Note: The default command in this image (
/bin/sh) has been over-ridden at the command line withlua -v. -
Now build images with different values for
LUA_VERSION. For exampledocker image build -f buildargs/lua.dockerfile --build-arg LUA_VERSION=5.1 --tag lua-dev:local-5.1 buildargs
NOTE: The value of LUA_VERSION is not available to the Docker
container at run time.
By default the value of any build args (ARG in the Dockerfile) do not
make it into the container runtime, although can find them with the
command docker container inspect.
But we can create ENV values during the build which will appear in the container’s environment.
It is possible to set an ENV value from an ARG value. For example:
Let’s go back to our original Lua development environment. We had
multiple Lua versions installed and the default version was hard coded
in the shell’s $ENV file. Let’s allow the default version to be
configured at build time. The files for this example are in
envfromargs.
-
Modify the dockerfile to to accept a build argument (and provide a default in case no value is provided)
ARG LUA_VERSION=5.3
-
Still in the dockerfile, create an environment value that will be seen at runtime
ENV LUA_VERSION_ENV=${LUA_VERSION}In this case both the ARG and the ENV values have the same value and the same purpose. There would be no problem in giving them the same same name, but it is potentially confusing.
-
Modify the
$ENVscript to make use of the correct environment name.ln -s /usr/bin/luac${LUA_VERSION_ENV} /usr/bin/luac ln -s /usr/bin/lua${LUA_VERSION_ENV} /usr/bin/lua -
Build the new image passing in the required default Lua version
doci build --tag lua-dev-f:5.1 --build-arg "LUA_VERSION=5.1" envfromargs
-
Test with
docker container run --rm -it --name myLuadev -e LUA_VERSION_ENV=5.3 lua-dev-f:5.1
and
docker container run --rm -it --name myLuadev -e LUA_VERSION_ENV=5.1 lua-dev-f:5.1
`docker compose <https://docs.docker.com/compose/>`__ is a Docker
tool that builds, provisions and starts multiple images that may be
required to create a development environment.
docker compose can be be considered as high level wrapper around the
various docker ... commands. You should be familiar with how the
lower level commands work before you start using docker compose in
earnest. Also Do not confuse docker compose with
container-orchestration tools such as
Kubernetes or
Swarm.
Note: docker compose is the replacement for docker-compose. More
information
here
The details of docker-compose are beyond the scope of this workshop, but a short demonstration might be useful. For more details refer to the Docker docs linked above.
This example provides a local developer easy access to a simple MongoDB development environment. It uses three three images:
Three containers for development
- A MongoDB database server, created by the MongoDB team on Docker Hub, with persistent storage provided through a named volume.
- A DB shell so the developer can run DB admin scripts. The directory
db-scriptsis bind mounted into the running container - A Python development environment with all needed dependencies
installed. The
srcdirectory is bind mounted into the container
All the containers are run via a compose file
docker-compose/docker/dev-compose.yaml
Let’s see it in action:
First run the MongoDB shell with the command
docker compose -f ./docker-compose/docker/dev-compose.yaml run shell
You will need to be patient when it first builds.
Once you are at the container prompt you can run any MongoDB admin
functions you want, for example db.adminCommand("listDatabases").
Use the quit() function to exit the MongoDB shell.
Notice that the containers are not removed (docc ls -a)
Now run the Python environment with
docker compose -f ./docker-compose/docker/dev-compose.yaml run python
A couple of example Python scripts are provided for you to play with.
Type exit when you get bored.
To tidy everything up use the command
docker compose -f ./docker-compose/docker/dev-compose.yaml down
So with a single YAML file (and two dockerfiles) we have provisioned a simple Python and MongoDB database development environment