Write Dockerfile completely in Bash/Bourne. Extensible and simple.
WARNING: I haven't had time to maintain this project. Use them at your own risk. Thank you for your understanding.
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:
copy
and paste
common codes between Dockerfile
s;
(Examples: FROM
, MAINTAINER
, ENV
,...);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;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;Dockerfile
;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.
Take a look at a quite minimal example in examples/Bockerfile.alpine
.
#!/usr/bin/env bash
# The default Alpine shell /bin/sh
ed_shell /bin/sh
ed_from alpine:3.8
ed_env --later Hello World
ed_bocker() {
:
}
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.
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.
Bocker
requires the popular tools:
bocker
script:
Bash
, base64
, grep
, sed
, gawk
;bash
or sh
, base64
.base64
is a basic tool from coreutils
package.
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.
-v
(--version
): Print the script version;-t
(--test
): Check if there is any problem with input.Bockerfile
All Bockerfile
s 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_
.
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.
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.
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.
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;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;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, source | ||
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 | ed_label | echo "LABEL foo=bar" |
Raw statement | echo "# Something" |
/bocker.sh
scriptThe 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
.
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.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.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.
This work is released the terms of MIT
license.
The author is Anh K. Huynh.