diff --git a/docker-cli-toolkit.md b/docker-cli-toolkit.md index 698f6e2..e255621 100644 --- a/docker-cli-toolkit.md +++ b/docker-cli-toolkit.md @@ -669,7 +669,7 @@ Deleted Containers: 90d006980a999176dd82e95119556cdf62431c26147bdbd3513e1733be1a5897 Deleted Images: -untagged: ghcr.io/uomresearchit/spuc@sha256:bc43ebfe7dbdbac5bc0b4d849dd2654206f4e4ed1fb87c827b91be56ce107f2e +untagged: spuacv/spuc@sha256:bc43ebfe7dbdbac5bc0b4d849dd2654206f4e4ed1fb87c827b91be56ce107f2e deleted: sha256:f03fb04b8bc613f46cc1d1915d2f98dcb6e008ae8e212ae9a3dbfaa68c111476 Total reclaimed space: 13.09MB diff --git a/dockerfiles.md b/dockerfiles.md index 75e2ef2..6f53953 100644 --- a/dockerfiles.md +++ b/dockerfiles.md @@ -6,310 +6,522 @@ exercises: 99 ::::::::::::::::::::::::::::::::::::::::::::::::::: objectives - Learn how to create your own container images using a `Dockerfile`. -- Learn about the core instruction set used in a `Dockerfile`. +- Introduce the core instructions used in a `Dockerfile`. - Learn how to build a container image from a `Dockerfile`. -- Learn how to run a container from a container image. +- Learn how to run a container from a *local* container image. :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::: questions - How can I create my own container images? - What is a `Dockerfile`? :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -We've been doing well picking things up from the SPUCS README! Let's keep the streak going. +The SPUC documentation just keeps on giving, let's keep the streak going! -There is another cool feature on there that we haven't used yet - the ability to add new Unicorn analysis features using plugins! Let's try that out. +There is another cool feature on there that we haven't used yet - +the ability to add new unicorn analysis features using plugins! Let's try that out. -The README says that we need to add a Python file at `/spuc/plugins/` that defines an endpoint for the new feature. +The docs says that we need to add a Python file at `/spuc/plugins/` that defines an endpoint for the new feature. -It would be very handy to be able to get some basic statistics about our Unicorns. Let's add a new plugin that will return a statstical analysis of the brightness of the Unicorns in the database. - -However you prefer - make a file `stats.py` with the following content: +It would be very handy to be able to get some basic statistics about our Unicorns. +Let's add a new plugin that will return a statistical analysis of the brightness of the unicorns in the database. +First lets make a file `stats.py` with the following content: ```python from __main__ import app from __main__ import file_path import pandas as pd +import os -@app.route('/stats', methods=['GET']) +@app.route("/stats", methods=["GET"]) def stats(): + if not os.path.exists(file_path): + return {"message": "No unicorn sightings yet!"} + with open(file_path) as f: df = pd.read_csv(f) + df = df.iloc[:, 1:] stats = df.describe() - return stats.to_json() + return stats.to_json() ``` -n.b. Don't worry if you're not familiar with Python or Pandas! This code will return some basic statistics about the data in the database. - -Now, from our previous lesson we know how to load this file! Let's use a bind mount to load this file into the container. Since we are debugging, we'll leave out the `-d` flag so we can see the output easily. - +Don't worry if you're not familiar with Python or Pandas. +Understanding this snippet of code is not our aim. +The code will return some statistics about the data in `file_path`. +We already know how to load this file. +Let's use a bind mount to share the file with the container. +Since we are debugging, we'll leave out the `-d` flag so we can see the output easily. ```bash -$ docker run --rm --name spuc_container -p 8321:8321 -v $PWD/print.config:/code/config/print.config -v spuc-volume:/code/output -v $PWD/stats.py:/spuc/plugins/stats.py -e EXPORT=true ghcr.io/uomresearchit/spuc:latest --units iulu +docker run --rm --name spuc_container -p 8321:8321 -v $PWD/print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -v $PWD/stats.py:/spuc/plugins/stats.py -e EXPORT=true spuacv/spuc:latest --units iulu ``` ```output Traceback (most recent call last): - File "/spuc/spuc.py", line 32, in + File "/spuc/spuc.py", line 31, in __import__(f"{plugin_dir}.{plugin[:-3]}") File "/spuc/plugins/stats.py", line 4, in import pandas as pd ModuleNotFoundError: No module named 'pandas' - -:::: Importing plugins :::: ``` -Oh... well what can we do about this? Clearly we need to install the `pandas` package in the container but how do we do that? - +Oh... well what can we do about this? +Clearly we need to install the `pandas` package in the container but how do we do that? We could do this interactively, but we know that won't survive a restart! Really what we need to do is **change** the image itself, so that it has `pandas` installed by default. - This takes us to one of the most fundamental features of Docker - the ability to create your own container images. ## Creating Docker Images -So how are images made? The answer is with a recipe! Named a `Dockerfile`. +So how are images made? With a recipe! -A `Dockerfile` is a text file that contains a list of instructions for producing a Docker container. The instructions are terminal command and build the container image up layer by layer. +Images are created from a text file that contains a list of instructions, called a `Dockerfile`. +The instructions are terminal commands, and build the container image up layer by layer. -We will start at the start, all Dockerfiles start with a `FROM` instruction. This sets the base image for the container. The base image is the starting point for the container, and all subsequent instructions are run on top of this base image. +All *Dockerfiles* start with a `FROM` instruction. +This sets the *base image* for the container. +The base image is the starting point for the container, and all subsequent instructions are run on top of this base image. -You can use **any** image as a base image and there are several offical images available on Docker Hub. For example, `ubuntu` for general purpose Linux, `python` for Python development, `alpine` for a lightweight Linux distribution, and many more. +You can use **any** image as a base image. +There are several *official* images available on Docker Hub which are very commonly used. +For example, `ubuntu` for general purpose Linux, `python` for Python development, `alpine` for a lightweight Linux distribution, and many more. -But of course the most natural fit for us right now is to use the SPUC image as a base image. This way we can be sure that our new image will have all the dependencies we need. - -Let's create a new file called `Dockerfile` (it **must** be called this!) and add the following content: +But of course, the most natural fit for us right now is to use the SPUC image as a base image. +This way we can be sure that our new image will have all the dependencies we need. +Let's create a new file called `Dockerfile` and add the following content: ```Dockerfile -FROM ghcr.io/uomresearchit/spuc:latest +FROM spuacv/spuc:latest ``` -This is the simplest possible `Dockerfile` - it just says that our new image will be based on the SPUC image. +This is the simplest possible Dockerfile - it just says that our new image will be based on the SPUC image. But what do we do with it? We need to build the image! -To do this we use the `docker build` command (short for `docker image build`). This command takes a `Dockerfile` and builds a new image from it and we will add a `-t` (tag) flag to give the image a name. +To do this we use the `docker build` command (short for `docker image build`). +This command takes a Dockerfile and builds a new image from it. +Just as when saving a file, we also need to name the image we are building. +We give the image a name with the `-t` (tag) flag: +```bash +docker build -t spuc-stats ./ +``` +```output +[+] Building 0.0s (5/5) FINISHED docker:default + => [internal] load build definition from Dockerfile 0.0s + => => transferring dockerfile: 61B 0.0s + => [internal] load metadata for docker.io/spuacv/spuc:latest 0.0s + => [internal] load .dockerignore 0.0s + => => transferring context: 2B 0.0s + => CACHED [1/1] FROM docker.io/spuacv/spuc:latest 0.0s + => exporting to image 0.0s + => => exporting layers 0.0s + => => writing image sha256:ccde35b1f9e872bde522e9fe91466ef983f9b579cffc2f457bff97f74206e839 0.0s + => => naming to docker.io/library/spuc-stats 0.0s + ``` + +Congratulations, you have now built an image! +The command built a new image called `spuc-stats` from the `Dockerfile` in the current directory. + +:::::::::::: spoiler + +## Context + +By default, the `docker build` command looks for a file called `Dockerfile` in the path specified by the last argument. + +This last argument is called the *build context*, and it **must** be the path to a directory. + +It is very common to see `.` or `./` used as the build context, both of which refer to the current directory. +All of the instructions in the `Dockerfile` are run as if we were in the build context directory. + +:::::::::::::::::::: + +:::::::::::: spoiler + +### The Dockerfile name + +As mentioned before, by default the `docker build` command looks for a file called `Dockerfile`. + +However, you can specify a different file name using the `-f` flag. +For example, if your Dockerfile is called `my_recipe` you can use: ```bash -$ docker build -t spuc-stats ./ +docker build -t spuc-stats -f my_recipe ./ +``` + +:::::::::::::::::::: + +If you now list the images on your system you should see the new image `spuc-stats` listed: +```bash +docker image ls +``` +```output +spuacv/spuc latest ccde35b1f9e8 25 hours ago 137MB +spuc-stats latest 21210c129ca9 5 minutes ago 137MB ``` +We can now run this image in the same way we would run any other image: +```bash +docker run --rm spuc-stats +``` +```output + + \ + \ + \\ + \\\ + >\/7 + _.-(ยบ \ + (=___._/` \ ____ ____ _ _ ____ + ) \ |\ / ___|| _ \| | | |/ ___| + / / ||\ \___ \| |_) | | | | | + / > /\\\ ___) | __/| |__| | |___ + j < _\ |____/|_| \____/ \____| + _.-' : ``. + \ r=._\ `. Space Purple Unicorn Counter + <`\\_ \ .`-. + \ r-7 `-. ._ ' . `\ + \`, `-.`7 7) ) + \/ \| \' / `-._ + || .' + \\ ( + >\ > + ,.-' >.' + <.'_.'' + <' -This command will build a new image called `spuc-stats` from the `Dockerfile` in the current directory. -We can now run this image in the same way we run any other image: +Welcome to the Space Purple Unicorn Counter! + +:::: Units set to Imperial Unicorn Hoove Candles [iuhc] :::: + +:: Try recording a unicorn sighting with: + curl -X PUT localhost:8321/unicorn_spotted?location=moon\&brightness=100 +:: No plugins detected +``` + +So we have a copy of the SPUC image with a new name, but nothing has changed! +In fact, we can pass all the same arguments to the `docker run` command as we did before: ```bash -$ docker run --rm --name spuc_container -p 8321:8321 -v $PWD/print.config:/code/config/print.config -v spuc-volume:/code/output -v $PWD/stats.py:/spuc/plugins/stats.py -e EXPORT=true spuc-stats --units iulu +docker run --rm --name spuc-stats_container -p 8321:8321 -v $PWD/print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -v $PWD/stats.py:/spuc/plugins/stats.py -e EXPORT=true spuc-stats --units iulu ``` ```output Traceback (most recent call last): - File "/spuc/spuc.py", line 32, in + File "/spuc/spuc.py", line 31, in __import__(f"{plugin_dir}.{plugin[:-3]}") File "/spuc/plugins/stats.py", line 4, in import pandas as pd ModuleNotFoundError: No module named 'pandas' ``` -Ok! Back where we were but with some new skills! - -Let's add that dependancy. We do this by adding a `RUN` instruction to the `Dockerfile`. This instruction runs a command in the container and then saves the result as a new layer in the image. +We are back where we were, but we can now start to make this container image our own! +Let's first fix that dependency problem. +We do this by adding a `RUN` instruction to the `Dockerfile`. +This instruction runs a command in the container and then saves the result as a new layer in the image. In this case we want to install the `pandas` package so we add the following lines to the `Dockerfile`: - ```Dockerfile RUN pip install pandas ``` This will install the `pandas` package in the container using Python's package manager `pip`. - Now we can build the image again: - ```bash $ docker build -t spuc-stats ./ ``` ```output -[...] -Installing collected packages: pytz, tzdata, six, numpy, python-dateutil, pandas -Successfully installed numpy-2.1.2 pandas-2.2.3 python-dateutil-2.9.0.post0 pytz-2024.2 six-1.16.0 tzdata-2024.2 -WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable.It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning. - ---> Removed intermediate container 642bfe617ba4 - ---> 6ec83b86fbd1 -Successfully built 6ec83b86fbd1 -Successfully tagged spuc-stats:latest +[+] Building 11.1s (6/6) FINISHED docker:default + [...] + => CACHED [1/2] FROM docker.io/spuacv/spuc:latest 0.0s + => [2/2] RUN pip install pandas 10.5s + => exporting to image 0.4s + => => exporting layers 0.4s + => => writing image sha256:e548b862a5c4dd91551668e068d4ad46e6a25d3a3dbed335e780a01f954a2c26 0.0s + => => naming to docker.io/library/spuc-stats 0.0s ``` -It is worth noting that we can ignore this warning! We are building a container image, not installing software on our host machine. - -Now we can run the image again: +You might have noticed a warning about running `pip` as the root user. +We are building a container image, not installing software on our host machine, +so we can ignore this warning. +Let's run the image again: ```bash -$ docker run --rm --name spuc_container -p 8321:8321 -v $PWD/print.config:/code/config/print.config -v spuc-volume:/code/output -v $PWD/stats.py:/spuc/plugins/stats.py -e EXPORT=True spuc-stats --units iulu +docker run --rm --name spuc-stats_container -p 8321:8321 -v $PWD/print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -v $PWD/stats.py:/spuc/plugins/stats.py -e EXPORT=true spuc-stats --units iulu ``` ```output [...] Welcome to the Space Purple Unicorn Counter! +[...] +:::: Plugins loaded! :::: +:: Available plugins + stats.py -:::: Units set to Intergalactic Unicorn Luminiocity Units [iulu] :::: - -:::: Plugins loaded :::: -['stats.py'] - -:: Try recording a unicorn sighting with: - curl -X PUT localhost:8321/unicorn_spotted?location=moon\&brightness=100 +[...] ``` -And now we can see that the `pandas` package is installed and the plugin is loaded! - -Let's try out the new endpoint: +It worked! We no longer get the error about the missing `pandas` package, and the plugin is loaded! +Let's try out the new endpoint +(you may want to do this from another terminal, +or exit with `Ctrl+C` and re-run with `-d` first): ```bash -$ curl localhost:8321/stats +curl localhost:8321/stats ``` ```output -{"count":{"count":3.0,"mean":1.0,"std":1.0,"min":0.0,"25%":0.5,"50%":1.0,"75%":1.5,"max":2.0},"brightness":{"count":3.0,"mean":10.0,"std":0.0,"min":10.0,"25%":10.0,"50%":10.0,"75%":10.0,"max":10.0}} +{"brightness":{"count":6.0,"mean":267.3333333333,"std":251.7599385658,"min":18.0,"25%":93.75,"50%":219.5,"75%":344.5,"max":709.0}} ``` And there we have it! We have created our own container image with a new feature! -But why stop here? We could keep modifing the image to make it more how we would like by default. +But why stop here? We could keep modifying the image to make it more how we would like by default. ## COPY -The first thing to look at is that it is a bit annoying having to bind mount the `stats.py` file every time we run the container. This makes sense for development but we would like to distribute the image with the plugin already installed. +It is a bit annoying having to bind mount the `stats.py` file every time we run the container. +This makes sense for development, because we can potentially modify the script while the container runs, +but we would like to distribute the image with the plugin already installed. We can add this file to the image itself using the `COPY` instruction. +This copies files from the host machine into the container image. +It takes two arguments: the source file on the host machine and the destination in the container image. -The `COPY` instruction copies files from the host machine into the container image. It takes two arguments: the source file on the host machine and the destination in the container image. - -Let's add the `COPY` instruction to the `Dockerfile`: - +Let's add it to the `Dockerfile`: ```Dockerfile -FROM ghcr.io/uomresearchit/spuc:latest - -RUN pip install pandas - COPY stats.py /spuc/plugins/stats.py ``` Now we can build the image again: - ```bash -$ docker build -t spuc-stats ./ +docker build -t spuc-stats ./ +``` +```output +[...] + => [1/3] FROM docker.io/spuacv/spuc:latest 0.0s + => [internal] load build context 0.0s + => => transferring context: 287B 0.0s + => CACHED [2/3] RUN pip install pandas 0.0s + => [3/3] COPY stats.py /spuc/plugins/stats.py 0.0s + => exporting to image 0.0s + [...] ``` -And run the image, this time without the bind mount for the `stats.py` file: +:::::::::::: spoiler + +## Layers + +You might have now noticed that on every build we are getting messages like `CACHED [2/3]...` above. +Every instruction* in a Dockerfile creates a new `layer` in the image. + +Each layer is saved with a specific hash. +If the set of instructions up to that layer remain unchanged, +Docker will use the cached layer, instead of rebuilding it. +This results in a lot of time and space being saved! + +In the case above, we had already run the `FROM` and `RUN` instructions in a previous build. +Docker was able to use the cached layers for those 2 instructions, +and only had to do some work for the `COPY` layer. + +:::::::::::::::::::: + +And run the image again, but this time without the bind mount for the `stats.py` file: ```bash -$ docker run --rm --name spuc_container -p 8321:8321 -v $PWD/print.config:/code/config/print.config -v spuc-volume:/code/output -e EXPORT=True spuc-stats --units iulu +docker run --rm --name spuc-stats_container -p 8321:8321 -v $PWD/print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -e EXPORT=true spuc-stats --units iulu ``` ```output [...] -:::: Plugins loaded :::: -['stats.py'] +Welcome to the Space Purple Unicorn Counter! +[...] +:::: Plugins loaded! :::: +:: Available plugins + stats.py [...] ``` -If we check the logs we can see that the plugin is still loaded! - -And again... why stop there? It is annoying having to mount the `print.config` file every time we run the container. We could add this file to the image as well! +The plugin is still loaded! +And again... why stop there? +We've already configured the print how we like it, so lets add it to the image as well! ```Dockerfile -FROM ghcr.io/uomresearchit/spuc:latest - -RUN pip install pandas - -COPY stats.py /spuc/plugins/stats.py -COPY print.config /code/config/print.config +COPY print.config /spuc/config/print.config ``` -Rebuilding and running (without the bind mount for `print.config`): +Now we rebuild and re-run (without the bind mount for `print.config`): +```bash +docker build -t spuc-stats ./ +docker run --rm --name spuc_container -p 8321:8321 -v spuc-volume:/spuc/output -e EXPORT=True spuc-stats --units iulu +``` +```output +[...] + => [1/4] FROM docker.io/spuacv/spuc:latest 0.0s + => [internal] load build context 0.0s + => => transferring context: 152B 0.0s + => CACHED [2/4] RUN pip install pandas 0.0s + => CACHED [3/4] COPY stats.py /spuc/plugins/stats.py 0.0s + => [4/4] COPY print.config /spuc/config/print.config 0.0s + => exporting to image 0.0s +[...] +Welcome to the Space Purple Unicorn Counter! +[...] +``` +OOh! a unicorn! lets record it! ```bash -$ docker build -t spuc-stats ./ -$ docker run --rm --name spuc_container -p 8321:8321 -v spuc-volume:/code/output -e EXPORT=True spuc-stats --units iulu +curl -X PUT localhost:8321/unicorn_spotted?location=saturn\&brightness=87 +``` +```output +{"message":"Unicorn sighting recorded!"} +``` +and the logs confirm copying the print config worked: +```bash +docker logs spuc_container +``` +```output +[...] +::::: Unicorn number 7 spotted at saturn! Brightness: 87 iulu ``` +The `run` command is definitely improving! Is there anything else we can do to make it even better? + ## ENV We can also set environment variables in the `Dockerfile` using the `ENV` instruction. -These can always be overridden when running the container but it is useful to set defaults. And we like the `EXPORT` variable so let's add that to the `Dockerfile`: - +These can always be overridden when running the container, as we have done ourselves, but it is useful to set defaults. +We like the `EXPORT` variable set to `True`, so let's add that to the `Dockerfile`: ```Dockerfile -FROM ghcr.io/uomresearchit/spuc:latest - -RUN pip install pandas - -COPY stats.py /spuc/plugins/stats.py -COPY print.config /code/config/print.config - ENV EXPORT=True ``` -Rebuilding and running (without the `-e EXPORT=True` flag): - +Rebuilding and running (without the `-e EXPORT=True` flag) results in: ```bash -$ docker build -t spuc-stats ./ -$ docker run --rm --name spuc_container -p 8321:8321 -v spuc-volume:/code/output spuc-stats --units iulu +docker build -t spuc-stats ./ +docker run --rm --name spuc-stats_container -p 8321:8321 -v spuc-volume:/spuc/output spuc-stats --units iulu ``` ```output +[...] + => [1/4] FROM docker.io/spuacv/spuc:latest 0.0s + => [internal] load build context 0.0s + => => transferring context: 61B 0.0s + => CACHED [2/4] RUN pip install pandas 0.0s + => CACHED [3/4] COPY stats.py /spuc/plugins/stats.py 0.0s + => CACHED [4/4] COPY print.config /spuc/config/print.config 0.0s + => exporting to image 0.0s +[...] +Welcome to the Space Purple Unicorn Counter! [...] :::: Unicorn sightings export activated! :::: :: Try downloading the unicorn sightings record with: curl localhost:8321/export ``` -We can see that the `EXPORT` variable is now set to `True` by default! +The `EXPORT` variable is now set to `True` by default! -## ENTRYPOINT +:::::::::::: spoiler -We're on a bit of a roll here! Let's add one more modification to the image. +## Layers - continued -Let's change away from those imperial units by default. +You might have noticed that the `ENV` instruction did not create a new layer in the image. -We can do this by changing the `ENTRYPOINT` instruction in the `Dockerfile`. So far we have been adding `--units iulu` to the end of the `docker run` which overwrites the `command`. +This instruction is a bit special, as it only modifies the configuration of the image. +The environment is set on every instruction of the dockerfile, so it is not saved as a separate layer. -If we move this to the `ENTRYPOINT` instruction then it will be run every time the container is started. We'll also add a `CMD` instruction with an empty list as the CMD was set in the SPUCS container base and we don't want to set `--units` twice. +However, environment variables *can* have an effect on instructions bellow it. +Because of this, moving the `ENV` instruction will *change* the layers, and the cache is no longer valid. +We can see this by moving the `ENV` instruction in our `Dockerfile` before the RUN command: ```Dockerfile -FROM ghcr.io/uomresearchit/spuc:latest +FROM spuacv/spuc:latest + +ENV EXPORT=True RUN pip install pandas COPY stats.py /spuc/plugins/stats.py -COPY print.config /code/config/print.config - -ENV EXPORT=True +COPY print.config /spuc/config/print.config +``` -ENTRYPOINT ["python", "/spuc/spuc.py", "--units", "iulu"] -CMD [] +If we now try to build again, we will get this output: +```bash +docker build -t spuc-stats ./ +``` +```output +[+] Building 10.4s (9/9) FINISHED docker:default + => [internal] load build definition from Dockerfile 0.0s + => => transferring dockerfile: 187B 0.0s + => [internal] load metadata for docker.io/spuacv/spuc:latest 0.0s + => [internal] load .dockerignore 0.0s + => => transferring context: 2B 0.0s + => CACHED [1/4] FROM docker.io/spuacv/spuc:latest 0.0s + => [internal] load build context 0.0s + => => transferring context: 61B 0.0s + => [2/4] RUN pip install pandas 9.8s + => [3/4] COPY stats.py /spuc/plugins/stats.py 0.0s + => [4/4] COPY print.config /spuc/config/print.config 0.0s + => exporting to image 0.5s + => => exporting layers 0.5s + => => writing image sha256:5a64cc132a7cbbc532b9e97dd17e5fb83239dfe42dae9e6df4d150c503d73691 0.0s + => => naming to docker.io/library/spuc-stats 0.0s ``` -Notice that we use array syntax for the `ENTRYPOINT` instruction. This is because the `ENTRYPOINT` instruction can take a list of arguments and the array syntax ensures that the arguments are passed correctly. +As you can see, the first layer is cached, but everything after the `ENV` instruction is rebuilt. +Our environment variable has absolutely no effect on the `RUN` instruction, but Docker does not know that. +The only thing that matters is that it *could* have had an effect. -Let's give this a try, dropping the unnecessary `--units iulu` from the `docker run` command: +It is therefore recommended that you put the `ENV` instructions only when they are needed. +A similar thing happens with the `ENTRYPOINT` and `CMD` instructions, which we will cover next. +Since these are not needed at all during the build, they are best placed at the end of the `Dockerfile`. + +:::::::::::::::::::: + +## ENTRYPOINT and CMD + +We're on a bit of a roll here! Let's add one more modification to the image. +Let's change away from those imperial units by default. + +We can do this by changing the default command in the `Dockerfile`. +As you may remember, the default command is composed of an *entrypoint* and a *command*. +We can modify either of them in the Dockerfile. +Just to make clear wheat the full command is directly from our dockerfile, lets write down both: +```Dockerfile +ENTRYPOINT ["python", "/spuc/spuc.py"] +CMD ["--units", "iulu"] +``` + +Notice that we used an array syntax. +Both the `ENTRYPOINT` and `CMD` instructions can take a list of arguments, +and the array syntax ensures that the arguments are passed correctly. + +Let's give this a try, dropping the now unnecessary `--units iulu` from the `docker run` command: ```bash -$ docker build -t spuc-stats ./ -$ docker run --rm --name spuc_container -p 8321:8321 -v spuc-volume:/code/output spuc-stats +docker build -t spuc-stats ./ +docker run --rm --name spuc-stats_container -p 8321:8321 -v spuc-volume:/spuc/output spuc-stats ``` ```output +[...] + => [1/4] FROM docker.io/spuacv/spuc:latest 0.0s + => CACHED [2/4] RUN pip install pandas 0.0s + => CACHED [3/4] COPY stats.py /spuc/plugins/stats.py 0.0s + => CACHED [4/4] COPY print.config /spuc/config/print.config 0.0s + => exporting to image 0.0s [...] :::: Units set to Intergalactic Unicorn Luminiocity Units [iulu] :::: [...] ``` -Much better! A far clearner command, much more customised for our use case! +Much better! A far cleaner command, much more customised for our use case! ## Back to reality -In this lession we have focused on modifying an image already containing a service. -This is a perfectly valid way of using Dockerfiles! But it is not the most common. +In this lesson we adjusted the SPUC image, which already contains a service. +This is a perfectly valid way of using Dockerfiles! +But it is not the most common. -While you can base your images on any other public image, it is most common for developers to be creating containers 'from the ground up'. +While you can base your images on any other public image, +it is most common for developers to be creating containers 'from the ground up'. The most common practice is creating images from images like `ubuntu` or `alpine` and adding your own software and configuration files. - -An example of this is how the developers of the SPUC service created their image. The Dockerfile is reproduced below: +An example of this is how the developers of the SPUC service created their image. +The Dockerfile is reproduced below: ```Dockerfile FROM python:3.12-slim @@ -332,27 +544,29 @@ CMD ["--units", "iuhc"] From this we can see the developers: -* started with a `python:3.12-slim` image -* installed the required packages (from a file) using `pip` -* copied the source code and configuration files -* set the default entrypoint andcommand. - -This is a very common pattern for creating images and is the most common way to use Dockerfiles but it is important to realise that you can create images from **any** base image and customise them to your heart's content! - -## Summary +* started `FROM` a `python:3.12-slim` image +* Use `RUN` to install the required packages (from a file) using `pip` +* `COPY` the source code and configuration files +* set the default `ENTRYPOINT` and `CMD`. -In this lesson we have seen how to create our own container images using a `Dockerfile`. +There are also two other instructions in this Dockerfile that we haven't covered yet. -We've added new layers with new packges installed using `RUN`, we've added files using `COPY`, set environment variables using `ENV`, and changed the default command using `ENTRYPOINT` and `CMD`. +* `WORKDIR` sets the working directory for the container. + It is used to create a directory and then change into it. + You may have noticed before that when we exec into the SPUC container we start in the `/spuc` directory. + All of the commands after a `WORKDIR` instruction are run from the directory it sets. +* `EXPOSE` is used to expose a port from the container to the host machine. + This is not strictly necessary, but it is a good practice to document which ports the service uses. -However, we are still encumbered with quite an unwieldy command to run the container. In the next lesson we will see how to make this easier by using `docker-compose`. :::::::::::::::::::::::::::::::::::::::: keypoints - You can create your own container images using a `Dockerfile`. -- A `Dockerfile` is a text file that contains a list of instructions for producing a Docker container. -- `FROM`, `RUN`, `COPY`, `ENV`, `ENTRYPOINT`, and `CMD` are some of the most imprtatn instructions that can be used in a `Dockerfile`. -- The `docker build` command is used to build a container image from a `Dockerfile`. -- You can run a container from a container image using the `docker run` command. +- A `Dockerfile` is a text file that contains a list of instructions to produce a container image. +- Each instruction in a `Dockerfile` creates a new `layer` in the image. +- `FROM`, `WORKDIR`, `RUN`, `COPY`, `ENV`, `ENTRYPOINT` and `CMD` are some of the most important instructions used in a `Dockerfile`. +- To build a container image from a `Dockerfile` you use the command: + `docker build -t ` +- You can run a container from a local image just like any other image, with docker run. :::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/md5sum.txt b/md5sum.txt index de59705..89e532c 100644 --- a/md5sum.txt +++ b/md5sum.txt @@ -5,11 +5,11 @@ "index.md" "1907984cd05b389a4a2738e7a5df7c4e" "site/built/index.md" "2024-10-17" "episodes/introduction.Rmd" "8414883e6a3a44416ad2db9c76a614b6" "site/built/introduction.md" "2024-10-17" "episodes/docker-desktop.Rmd" "8a4b72c36dc72e199382fe0df8deee36" "site/built/docker-desktop.md" "2024-10-17" -"episodes/docker-cli-toolkit.Rmd" "75e88eae95b3a38f4b63f3862bef8997" "site/built/docker-cli-toolkit.md" "2024-10-17" +"episodes/docker-cli-toolkit.Rmd" "163b413d45763e10bbdaae2c67f89094" "site/built/docker-cli-toolkit.md" "2024-10-17" "episodes/docker-volumes.Rmd" "40e951f12065995e4ba89c861bc3914f" "site/built/docker-volumes.md" "2024-10-17" "episodes/docker-hub.Rmd" "37e9663c271f8d7ca65cf82a46a068eb" "site/built/docker-hub.md" "2024-10-17" "episodes/docker-run-configuration.Rmd" "39b7459c7c7d8a893db77fa93b3030df" "site/built/docker-run-configuration.md" "2024-10-17" -"episodes/dockerfiles.Rmd" "d8460f32351ea61238082aba099dd723" "site/built/dockerfiles.md" "2024-10-17" +"episodes/dockerfiles.Rmd" "85b02cbb402abc00b05f42744ba04882" "site/built/dockerfiles.md" "2024-10-17" "episodes/docker-compose.Rmd" "632e13c3e8d913abb473e7395bad612e" "site/built/docker-compose.md" "2024-10-17" "instructors/instructor-notes.md" "fcee6075930831d9bb66fa7f6b944aa7" "site/built/instructor-notes.md" "2024-10-17" "learners/setup.md" "4b8c9c34769e6660e783abae271e5c59" "site/built/setup.md" "2024-10-17"