Dockerfiles sharing the same instructions but built from a different image - docker

What would be the best practice when I need to create multiple docker images that share the same instructions EXCEPT the FROM image?
For example, I want to build 3 different images - a Java stack, a Python stack, and a Rust stack. So I have 3 Dockerfile's each referencing a different FROM image. Then, in each of these Dockerfile, I have a long list of instructions that are exactly the same. I would rather not duplicate the instructions.

You can try passing image name as arguments if only image name is changing
Dockerfile:
ARG img
FROM $img
RUN echo “Building $img”
Then run build command on terminal:
sudo docker build . --build-arg img=busybox

Related

Make docker file from local image?

I have docker file which make image.
FROM public.ecr.aws/lambda/python:3.9
RUN export LANG=en_US.UTF-8
RUN export PYTHONUNBUFFERED=1
docker build -f dockers/mydocker -t python .
then I would like to make images from this image.
There are listed image named basic docker images
Then in another Dockerfile.
FROM python
ADD ./Pipfile* ./
RUN pipenv install --ignore-pipfile
When I try to build this dockerfile
There comes like this
FROM docker.io/library/python
Does this mean I can use local image to build next image?
I need to make local repository for this purpose ??
Or any other way to do this??
This is probably working fine, but you should be careful to pick names that don't conflict with standard Docker Hub image names.
A Docker image name has the form registry.example.com/path/name. If you don't explicitly specify a registry, it always defaults to docker.io (this can't be changed), and if you don't specify a path either, it defaults to docker.io/library/name. This is true in all contexts – the docker build -t option, the docker run image name, Dockerfile FROM lines, and anywhere else an image name appears.
That means that, when you run docker build -t python, you're creating a local image that has the same name as the Docker Hub python image. The docker build diagnostics are showing you the expanded name. It should actually be based on your local image, though; Docker won't contact a remote registry unless the image is missing locally or you explicitly tell it to.
I'd recommend choosing some unambiguous name here. You don't specifically need a Docker Hub account, but try to avoid bare names that will conflict with standard images.
# this will work even if you don't "own" this Docker Hub name
docker build -f Dockerfile.base -t whitebear/python .
FROM whitebear/python
...
(You may have some trouble seeing the effects of your base image since RUN export doesn't do anything; change those lines in the base image to ENV instead.)

Use console output in Dockerfile

I want to use some console output as the name of my base docker image.
Specifically, I have a chain of dependent docker build files so I am trying to automate this process. So for instance, the Dockerfile of one image derived1 depends on the base image base_image_namein the following scenario:
base_image_name/
Dockerfile
derived1/
Dockerfile
derived2/
Dockerfile
When the base image builds, it grabs its name from its current folder by using ${PWD##*/}. In this case, the base image's folder is called base_image_name, and so the base image is called company:base_image_name.
Then when the derived images build, they should just be able to figure out the base image's name by moving up a directory and looking at that directories name. So for instance, when build the company:derived1 image builds, it should look up one directory, see that it is called base_image_name, and from that infer that it should use the base image company:base_image_name.
I would like to have this structure several layers deep, so I want to automate it. To do that, I have tried several permutations of the syntax
FROM company:$(cd $PWD/../; echo ${PWD##*/})
but I can't seem to get it right. To understand what the command $(cd $PWD/../; echo ${PWD##*/}) is doing, just type it into your terminal..
echo $(cd $PWD/../; echo ${PWD##*/})
simply returns the name of the directory one level up. However, when I try to use this in a Dockerfile, I get the error
Error response from daemon: Dockerfile parse error line 1: FROM requires either one or three arguments
Could somebody please provide me with the correct syntax?
EDIT:
I also tried building the derived images with a build-arg, but that doesn't seem to work either:
build.sh:
BASE=$(cd $PWD/../../; echo ${PWD##*/})
echo "BASE="$BASE
docker build --build-arg BASE=${BASE} -t company:"${PWD##*/}" .
where the Dockerfile looks like
FROM company:$BASE
Specifically, this yields the build error:
BASE=base_image_name
Sending build context to Docker daemon 5.12kB
Step 1/3 : FROM company:$BASE
invalid reference format
So it seems that docker is not interpretting that build arg correctly.
Dockerfiles don't support shell syntax in general, except for some very limited environment variable expansion.
They do support ARGs that can be passed in from the command line, and an ARG can be used to define the image FROM. So you could start your Dockerfile with
ARG tag
FROM company:${tag:-latest}
and then build the image with
docker build --build-arg tag=$(cd $PWD/../; echo ${PWD##*/}) .
(which is involved enough that you might want to write it into a shell script).
At a very low level, it's also worth remembering that docker build works by making a tar file of the current directory, sending it across an HTTP connection to the Docker daemon, and running the build there. Once that process has happened, any notion of the host directory name is lost. In other words, even if the syntax worked, docker build also doesn't have the capability to know which host directory the Dockerfile is in.
Aha. Found it.
As Jonathon points out, it seems as though you can't easily pull stuff in from your environment into the build system. It seems that you must use Docker build-args.
The solution was to evaluate the variable in the terminal and pass that as a build-arg:
build.sh:
BASE=$(cd $PWD/../; echo ${PWD##*/})
echo "BASE="$BASE
docker build --build-arg BASE=${BASE} -t company:"${PWD##*/}" .
Then inside the Dockerfile of the derived image:
ARG BASE
FROM company:$BASE
You're trying to use bash command substitution in something that isn't consumed by bash.
The [Dockerfile reference[(https://docs.docker.com/engine/reference/builder/) indicates that environment variable substitution is supported by the FROM instruction.
You'll need to instead simply use an environment variable in FROM that you compute outside of the Dockerfile and pass to docker build.

How can I make docker to not eat up disk space if used in Continuous integration

I am playing with docker and plan to use it in a GitLab CI environment to package the current project state to containers and provide running instances to do reviews.
I use a very simple Dockerfile as follows:
FROM php:7.0-apache
RUN sed -i 's!/var/www/html!/var/www/html/public!g' /etc/apache2/sites-available/000-default.conf
COPY . /var/www/html/
Now, as soon as a I a new (empty) file (touch foobar) to the current directory and call
docker build -t test2 --rm .
again, a full new layer is created, containing all of the code.
If I do not create a new file, the old image seems to be nicely reused.
I have a half-way solution using the following Dockerfile:
FROM test2:latest
RUN sed -i 's!/var/www/html!/var/www/html/public!g'
/etc/apache2/sites-available/000-default.conf
COPY . /var/www/html/
After digging into that issue and switching the storage driver to overlay, this seems to be what I want - only a few bytes are added as a new layer.
But now I am wondering, how I could integrate this into my CI setup - basically I would need two different Dockerfiles - depending on whether the image already exists or it doesn't.
Is there a better solution for this?
Build your images with same tags or no tags
docker build -t myapp:ci-build ....
or
docker build ....
If you use same tags then old images will be untagged and will have "" as name. If you don't tag them then also they will have "" in name.
Now you can schedule below command
docker system prune -f
This will remove all dangling images containers etc
One suggestion is to use the command docker image prune to clean dangling images. This can save you a lot of space. You can run this command regularly in your CI.

Can a variable be used in docker FROM?

I am wondering if a env variable can be used in a docker from? Reason for this is to control the tagging. For example, say I have this line in my Dockerfile:
FROM myApp
What I want is this:
FROM myApp:${VERSION}
This way I can say docker build . myApp --build-arg VERSION=9
The process to build docker images for this app is the same. I don't want to have Dockerfiles that are almost identical just to use a different base image.If I want to build version 9, it should use version 9 of the base image.
Quoting this link
:
This is now possible if anyone comes here looking for answers: https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
FROM instructions support variables that are declared by any ARG instructions that occur before the first FROM.
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
For at least this docker version this is feasible
docker --version
docker version 18.09.8, build bfed4f5
It requires a preset variable in Dockerfile e.g.
ARG TAG=latest
FROM traefik:${TAG}
Then you can override this preset with the following
docker build --build-arg "TAG=2.2.8" -t my-app:$TAG
The version number will not show up during build. if you want to test if it works, reference a non-existing version - it will fail with: manifest my-app:version not found.
You could simply generate your Dockerfile from a template. Put
something like this in a Makefile:
MYTAG=latest
.PHONY: Dockerfile
Dockerfile: Dockerfile.in
sed 's/MYTAG/$(MYTAG)/' $< > $# || rm -f $#
Then you can run:
make MYTAG=8; docker build -t my-app-8 .
This would only make sense if you are frequently building images that
require a different tag in the FROM line.
It is not possible.
Although, you can use a variable tag like from myApp:latest and overwrite the latest tag when you're creating a new version.
Build your container programatically using buildah (It can take Dockerfile too).
So for your use-case:
VERSION=v0.1.0
myCon=$(buildah from myApp:${VERSION})
buildah config --cmd "sleep 1d" $myCon
buildah commit $myCon $USER/sleeping1d
You can obviously script it, save and invoke it, and one more advantage is
buildah doesn't need docker daemon running, which is great for CI. Also it's an open-source project, check out the project page.
BTW I saw this issue lately which is exactly what you want - https://github.com/projectatomic/buildah/issues/581
Unfortunately it's not possible to do that. The first line of your Dockerfile must be a FROM directive, and so that precludes the use of the ARG directive. There is a good answer there from larsks about generating a Dockerfile, but I'd also like to suggest merely creating different Dockerfiles and then specifying a particular one in your docker build command using the -f switch:
docker build -t codemiester/app:latest -f ./Dockerfile.apache2.ubuntu

Labelling images in docker

I've got a jenkins server monitoring a git repo and building a docker image on code change. The .git directory is ignored as part of the build, but I want to associate the git commit hash with the image so that I know exactly what version of the code was used to make it and check whether the image is up to date.
The obvious solution is to tag the image with something like "application-name-branch-name:commit-hash", but for many develop branches I only want to keep the last good build, and adding more tags will make cleaning up old builds harder (rather than using the jenkins build number as the image is built, then retagging to :latest and untagging the build number)
The other possibility is labels, but while this looked promising initially, they proved more complicated in practice..
The only way I can see to apply a label directly to an image is in the Dockerfile, which cannot use the build environment variables, so I'd need to use some kind of templating to produce a custom Dockerfile.
The other way to apply a label is to start up a container from the image with some simple command (e.g. bash) and passing in the labels as docker run arguments. The container can then be committed as the new image. This has the unfortunate side effect of making the image's default command whatever was used with the labelling container (so bash in this case) rather than whatever was in the original Dockerfile. For my application I cannot use the actual command, as it will start changing the application state.
None of these seem particularly ideal - has anyone else found a better way of doing this?
Support for this was added in docker v1.9.0, so updating your docker installation to that version would fix your problem if that is OK with you.
Usage is described in the pull-request below:
https://github.com/docker/docker/pull/15182
As an example, take the following Dockerfile file:
FROM busybox
ARG GIT_COMMIT=unknown
LABEL git-commit=$GIT_COMMIT
and build it into an image named test as anyone would do naïvely:
docker build -t test .
Then inspect the test image to check what value ended up for the git-commit label:
docker inspect -f '{{index .ContainerConfig.Labels "git-commit"}}' test
unkown
Now, build the image again, but this time using the --build-arg option:
docker build -t test --build-arg GIT_COMMIT=0123456789abcdef .
Then inspect the test image to check what value ended up for the git-commit label:
docker inspect -f '{{index .ContainerConfig.Labels "git-commit"}}' test
0123456789abcdef
References:
Docker build command documentation for the --build-arg option
Dockerfile reference for the ARG directive
Dockerfile reference for the LABEL directive
You can specify a label on the command line when creating your image. So you would write something like
docker build -t myproject --label "myproject.version=githash" .
instead of hard-coding the version you can also get it directly from git:
docker build -t myproject --label "myproject.version=`git describe`" .
To read out the label from your images you can use docker inspect with a format string:
docker inspect -f '{{index .Config.Labels "myproject.version"}}' myproject
If you are using docker-compose, you could add the following to the build section:
labels:
git-commit-hash: ${COMMIT_HASH}
where COMMIT_HASH is your environment variable, which holds commit hash.

Resources