ssb
ssb

Reputation: 7502

Command line templating in Dockerfiles

I am trying to build a Dockerfile that builds my project for multiple base images (e.g., Ubuntu 17.10 for multiple architectures)

I want to be able to write something like

FROM {{ ARCH }}/ubuntu:17.10
...

And have it resolve at build time to ubuntu:17.10 or ppc64le/ubuntu:17.10. Is there someway to do this?

Additionally a separate solution might be to have two docker files, but somehow include the common portions of the file into both the docker files.

Upvotes: 3

Views: 2872

Answers (4)

okainov
okainov

Reputation: 4654

TL;DR - Unfortunately this is not supported by Docker out-of-the box. It means, you need to do the stuff by yourself.

There are several libraries written especially for this task:

dockerfile-template allows you to write stuff like that in Dockerfile:

FROM %%BASE%%:%%TAG%%
...

And then process this docker-template file with the given values to produce the final Dockerfile. However it doesn't really support multiline replacement and it's written in Javascript (sic!).

Alternative solution could be to write your own simple templater like @Ricky Barillas suggested (also you could use existing template engines like jinja if you want more power).

Mind the disadvantage of all such home-baked solutions is that your Dockertemplate files will be going beyond the syntax of Dockerfile, so you won't be able to use Dockerfile linters/validators any more (only on the final output). Also they will become less "portable" in terms that to build your image one will need not only base Docker, but your templating scripts as well.

Upvotes: 1

danblack
danblack

Reputation: 14666

Modern tags of the official docker library operating systems systems tend to be multiarch now. Just building against FROM ubuntu:17.10 on the architecture will fetch the right image.

e.g.:

docker run ubuntu:17.10 uname -a
Unable to find image 'ubuntu:17.10' locally
17.10: Pulling from library/ubuntu
c3cef7a7aa54: Pull complete 
8cbf0d1d8127: Pull complete 
1b5d7d7519b4: Pull complete 
855c347561ac: Pull complete 
ec1530a7444c: Pull complete 
Digest: sha256:3b811ac794645dfaa47408f4333ac6e433858ff16908965c68f63d5d315acf94
Status: Downloaded newer image for ubuntu:17.10
Linux 93a4440468e9 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:22:24 UTC 2018 ppc64le ppc64le ppc64le GNU/Linux

Upvotes: 0

Ricky Barillas
Ricky Barillas

Reputation: 41

My first thought is to write a templating script that will replace your variable values to whatever is needed at the time. This values could be passed into the script when called or sourced from environment variables.

I'm thinking in particular of using python's string.Template module and using the delimiter $ rather than { since { might be used in your Dockerfile else where. $ is the default delimiter for string.Template. Also I would name this template file with a different extension since it Docker won't be able to build it with the templated variables inside.

Let's say your Dockerfile.template looks like this:

FROM $ARCH/ubuntu:17.10
...

you could then set and environment variable ARCH=ppc64le in your session and run a short script like this:

import os
import string

with open('Dockerfile.template', 'r') as f:
    template = string.Template(f.read())

with open('Dockerfile', 'w') as f:
    args = {key: value.strip() for (key, value) in os.environ.items()}
    f.write(template.substitute(**args))

These two lines read your templated file and create a string.Template object. The default delimiter for string.Template is $ so it complies with your template.

with open('Dockerfile.template', 'r') as f:
    template = string.Template(f.read())

This just sets us up to create and write to your actual Dockerfile

with open('Dockerfile', 'r') as f:

This fancy line is where the environment variables come in to play. os.environ is a mapping object of your os's environment variables. os.environ.items() unpacks each pair into its respective key:value tuple. value.strip() just cleans up the value to make sure there's no weird whitespacing. All of this is packed into dictionary comprehension, where the variable key is the name of the environment variable, and in this case the templated variable, and it maps the the value of this variable

args = {key: value.strip() for (key, value) in os.environ.items()}

lastly we have this line. template.substitute() takes in a mapping object and tries to substitute all of the keys with its values. **args expands the dictionary.

f.write(template.substitute(**args))

Upvotes: 2

Mano Marks
Mano Marks

Reputation: 8819

You can use a build argument and use docker build --build-arg foo=bar to build the image.

Upvotes: 3

Related Questions