Write Dockerfile completely in Bash/Bourne. Extensible and simple.
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.

README.md

Table of contents

Description

Bocker makes your Dockerfile reusable. The name is combined from B(ash) and (D)ocker.

Dockerfile is a mix of shell commands, run-time settings and stuff. It looks simple at first, but in a long run, you'll have some problems:

  • Have to copy and paste common codes between Dockerfiles; (Examples: FROM, MAINTAINER, ENV,...);
  • No way to do some basic checks. For example, the COPY /foo /bar will raise an error if /foo doesn't exist; if that's the case, you have no way to tell COPY to continue;
  • It's hard to define and call a sub-routine in RUN statement, because RUN is one-line statement. Yes, you can try to do that with a mess of unreadable and un-maintainable codes;
  • No way to include some useful parts from other Dockerfile;
  • No way to create the full Dockerfile of an image and its ancestors.

This project is to solve these problems. It will read some Bash source files, and write new Dockerfile to STDOUT. The output is cacheable, and can be executed by docker build command.

Getting started

A minimal example

Take a look at a quite minimal example in examples/Bockerfile.alpine. To use this file, type the following commands

$ cd examples/
$ ../bocker.sh Bockerfile.alpine > Dockerfile.alpine

New contents are exactly a Dockerfile for your build.

More examples

Overloading? Improve caching with --later? Use ship instead of ADD / COPY commands?

See more from examples/README.md at https://github.com/icy/bocker/blob/master/examples/README.md or a collection of Bockerfile at https://github.com/icy/docker/tree/master/bocker.

Install and Usage

Requirements

Bocker requires the popular tools:

  • On local machine where you run bocker script: Bash, base64, grep, sed, gawk;
  • On base image: bash or sh, base64.

base64 is a basic tool from coreutils package.

Installation

There is only one Bash script bocker.sh. Put this script in one of your binary directories, and start it with Bockerfile

$ bocker.sh MyBockerfile >/dev/null # to see if there is any error
$ bocker.sh MyBockerfile            # to see Dockerfile output

The output is written to STDOUT. You should check if there is anything wrong from STDERR, because Bocker is unable to check if your source file has problem.

Command line options

  • -v (--version): Print the script version;
  • -t (--test): Check if there is any problem with input.

Syntax of Bockerfile

All Bockerfiles are Bash source files. That means you can write your source in some small files, and include them in other files.

The first rule is that every method is started with ed_.

Front matter

There are some basic methods to define your meta information. For your overview, let's see:

ed_from        YOUR_BASE_IMAGE
ed_maintainer  "YOUR INFORMATION"

ed_env         FOO /bar/
ed_expose      80
ed_volume      /example.net/
ed_cmd         '["/supervisor.sh", "foo", "bar"]'
ed_ship        foobar
....

Think of FROM, MAINTAINER, EXPOSE. They are:

  • ed_from: Define your FROM information;
  • ed_maintainer: Define your MAINTAINER information;
  • ed_env: Define new ENV instruction; Use --later option if your environment is only needed at the run-time;
  • ed_expose: Specify a list of exposed ports;
  • ed_volume: Specify a list of volumes;
  • ed_onbuild: Specify trigger on the descendant image build;
  • ed_cmd: Define your CMD statement;
  • ed_user: Define your USER statement; Must use with --later option;
  • ed_copy: Define your COPY statement; If you want to have ADD, use --add option. Must use with the option --later;
  • ed_entrypoint: Define your ENTRYPOINT statement;
  • ed_ship: Define a list of methods to be shipped to the image; That means, you can define a function ed_foobar, and call ed_ship ed_foobar to make this function available to Docker at build time and run time. Actually, functions' definitions are written to the file /bocker.sh in the result image, and that will be included at every RUN;
  • ed_ship --later: Like ed_ship, but the contents are shipped at the very end of Dockerfile. This is very useful when the functions are only needed at the run-time, because that really speeds up your build process. See example in examples/lib/debian.sh.)
  • ed_reuse: Load the Bockerfile(s) specified in argument, and re-use ed_docker from that source if any. All ed_docker definitions are additive in order provided.

All these commands can be used multiple times, and/or be put in your base libraries. (See examples/lib/core.sh.)

The last statement of ed_from (ed_maintainer, ed_cmd, ed_entrypoint) will win; other functions have additive effect.

Main matter

You can define your set of methods as Bash functions, each of them has a name started by ed_. For example, in examples/lib/debian.sh, you will see ed_apt_clean that removes unused apt-get variable data to minimize the size of your result image.

Main function: ed_bocker

This is a must-have function. Bocker will raise error if you don't define it.

This function should not contain any function from PREAMBLE section.

It can have some special functions

  • ed_copy: Define your COPY statement;
  • ed_add: Define your ADD statement;
  • ed_user: Define your USER statement;
  • ed_workdir: Define your WORKDIR statement;
  • ed_run: Define your RUN statement;
  • ed_group: Group multiple methods into a single RUN statement.

Bocker will read the contents of this ed_bocker function, replace every appearance of ed_* by __ed_ship_method ed_*. That means, if you type ed_apt_clean, Bocker will invoke __ed_ship_method ed_apt_clean for you.

Because this is actually a replace-execute trick, it's your duty to make your definition of ed_bocker as simple as possible. Don't use complex stuff like expansion and (WHO KNOWS?) If you have to do that, put your stuff under some functions, ship them to the image with ed_ship, and that's just enough.

Dockerfile vs. Bockerfile

Facts

  • Dockerfile statements are ordered. First declared first run. In Bockerfile, most stuff in PREAMBLE are un-ordered;
  • Dockerfile supports array form of ENV, EXPOSE, VOLUME; but Bockerfile doesn't. This way helps Bockerfile to glue declarations from multiple library files into a single statement;
  • To group RUN commands in Dockerfile, you have to use && and remove RUN from the later statements. In Bockerfile, you simply use ed_group. See this example;
  • To declare a Bash function and use them in every RUN statement, you may put that definition in a file, use COPY to transfer the file to the container and load it, e.g, RUN source /mylib.sh; ...; You can love this way or not. In Bockerfile, you simply use ed_ship for build-time methods, and ed_ship --later for run-time methods with a minimum number of layers.

Here is a table for quick reference.

Purpose Dockerfile Bockerfile (Preamble) ed_bocker
Base image FROM ed_from
Base script ed_reuse
Base script ed_source, sourc
Maintainer MAINTAINER ed_maintainer
Volume expose VOLUME ed_volume
Port expose EXPOSE ed_expose
Init script ENTRYPOINT ed_entrypoint
Init command CMD ed_cmd
Int command ONBUILD ed_onbuild
Variable ENV ed_env [--later]
Build command RUN ed_bocker ed_foo, ed_run
Build command ADD ed_copy --add --later ed_add
Build command COPY ed_copy --later ed_copy
Build command USER ed_user --later ed_user
Build command WORKDIR TODO ed_workdir
Declare method N/A ed_ship [--later]
Grouping && ed_group
Label LABEL TODO echo "LABEL foo=bar"
Raw statement secho "# Something"

/bocker.sh script

The result image has /bocker.sh script that contains (almost) all your functions.

When you use ed_ship or invoke some command inside your ed_bocker, your function definitions are saved originally (except the comments, of course) to the /bocker.sh script in the result image.

This script only contains functions, and if you provide any arguments to it, they are considered as command in the environment where your functions are defined. For example

# ed_ship --later my_method
/bocker.sh ed_my_method
# /bocker.sh find

will invoke ed_my_method (or find command) that you have shipped.

Because of this, you can simply define a start-up function, and use /bocker.sh to call them. That exactly means, bocker.sh can be used as your ENTRYPOINT.

Important notes

  • ed_bocker is executed locally at run time, on your local machine. This is dangerous. Please don't add too much codes inside ed_bocker. That function should only contain ed_* methods.
  • Any RUN generated by Bocker has option -xeu set by default; That means any error will stop. If you want to have something else, you can always do that in you ed_* definition.

History

When the project is started, its name is EDocker, that's why you see ed_ prefixes. EDocker isn't a good name, hence you see Bocker now.

License. Author

This work is released the terms of MIT license. The author is Anh K. Huynh.