I have some really large docker files that I would like to refactor and code share.
A lot of them share a lot of build steps (i.e. installing specific dependencies from github), but some of them have different base images.
Is there a good way to create functions to abstract away and share this logic. Ideally I would create a Docker library with functions like:
def install_opencv():
... DO stuff ...
def install_aws_streams():
... DO stuff ...
... other helpful install commands ...
And then I could import them in the relevant docker files:
DockerFileA
import my_custom_docker_lib
From baseX
install_opencv()
install_aws_streams()
DockerFileB
import my_custom_docker_lib
From baseY
install_opencv()
install_aws_streams()
CodePudding user response:
Each docker image consists of layers, and you are supposed to build the effective layer model. This approach differs from writing a program in some procedural language where you want to decouple the spaghetti code in logical modules.
If you are confident about your existing layers, then you can check out the templates approach, like in many popular images like Postgres:
...
COPY docker-entrypoint.sh /usr/local/bin/
{{ if .major >= 11 then "" else ( -}}
RUN ln -s usr/local/bin/docker-entrypoint.sh / # backwards compat
{{ ) end -}}
ENTRYPOINT ["docker-entrypoint.sh"]
...
CodePudding user response:
A Dockerfile has no notion of "functions" or "subroutines". It executes the RUN
instructions and other commands in linear order; there aren't even loops or conditionals.
In principle it could be possible to build up something like this with a series of Dockerfiles, but you'd have an extremely manual build system. The trick you could use here is using a Dockerfile ARG
to dynamically provide the FROM
image; then you could have a Dockerfile fragment that installed each of these pieces individually, and have a series of docker build --build-arg base_image=...
commands to stitch them together.
If it's possible to do each of these installations in a single RUN
command then you could build a shell script of installation functions
install_opencv() {
apt-get update
apt-get install ...
}
install_aws_streams() {
...
}
You could then bring this library of installation commands into your image and RUN
them.
FROM ubuntu:22.04
COPY installers.sh /usr/local/bin
RUN . installers.sh && install_opencv
RUN . installers.sh && install_aws_streams
(Remember to use standard Bourne shell syntax and not bash extensions, especially if you'll be targeting Alpine images: shell functions do not begin with the word function
, and prefer .
to non-standard source
. Also note that the entire build sequence will be repeated if the installer function script changes at all.)
This setup does conflict a little bit with how I'd expect a typical image to work. An image would usually package only a single application. That image would also work outside a container, and a language-specific dependency file would list out the things it needs. I'd then expect the Dockerfile to have at most one distribution-package-manager "install" command, that installed things needed to build C extensions, and then one laiguage-package-manager "install" command to install the actual application dependencies. A containerized application wouldn't necessarily expect a large library of dependencies to be available in the runtime environment.