Skip to content

Commit 9f994c9

Browse files
author
Solomon Hykes
committed
New build instruction: ONBUILD defines a trigger to execute when extending an image with a new build
Docker-DCO-1.1-Signed-off-by: Solomon Hykes <[email protected]> (github: shykes)
1 parent a51f5a2 commit 9f994c9

4 files changed

Lines changed: 92 additions & 1 deletion

File tree

buildfile.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,26 @@ func (b *buildFile) CmdFrom(name string) error {
108108
if b.config.Env == nil || len(b.config.Env) == 0 {
109109
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
110110
}
111+
// Process ONBUILD triggers if they exist
112+
if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
113+
fmt.Fprintf(b.errStream, "# Executing %d build triggers\n", nTriggers)
114+
}
115+
for _, step := range b.config.OnBuild {
116+
if err := b.BuildStep(step); err != nil {
117+
return err
118+
}
119+
}
120+
b.config.OnBuild = []string{}
111121
return nil
112122
}
113123

124+
// The ONBUILD command declares a build instruction to be executed in any future build
125+
// using the current image as a base.
126+
func (b *buildFile) CmdOnbuild(trigger string) error {
127+
b.config.OnBuild = append(b.config.OnBuild, trigger)
128+
return b.commit("", b.config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
129+
}
130+
114131
func (b *buildFile) CmdMaintainer(name string) error {
115132
b.maintainer = name
116133
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
@@ -712,7 +729,6 @@ func (b *buildFile) BuildStep(expression string) error {
712729
return nil
713730
}
714731

715-
716732
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
717733
if ret != nil {
718734
return ret.(error)

container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type Config struct {
9999
WorkingDir string
100100
Entrypoint []string
101101
NetworkDisabled bool
102+
OnBuild []string
102103
}
103104

104105
func ContainerConfigFromJob(job *engine.Job) *Config {

docs/sources/reference/builder.rst

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,64 @@ the image.
402402
The ``WORKDIR`` instruction sets the working directory in which
403403
the command given by ``CMD`` is executed.
404404

405+
3.11 ONBUILD
406+
------------
407+
408+
``ONBUILD [INSTRUCTION]``
409+
410+
The ``ONBUILD`` instruction adds to the image a "trigger" instruction to be
411+
executed at a later time, when the image is used as the base for another build.
412+
The trigger will be executed in the context of the downstream build, as if it
413+
had been inserted immediately after the *FROM* instruction in the downstream
414+
Dockerfile.
415+
416+
Any build instruction can be registered as a trigger.
417+
418+
This is useful if you are building an image which will be used as a base to build
419+
other images, for example an application build environment or a daemon which may be
420+
customized with user-specific configuration.
421+
422+
For example, if your image is a reusable python application builder, it will require
423+
application source code to be added in a particular directory, and it might require
424+
a build script to be called *after* that. You can't just call *ADD* and *RUN* now,
425+
because you don't yet have access to the application source code, and it will be
426+
different for each application build. You could simply provide application developers
427+
with a boilerplate Dockerfile to copy-paste into their application, but that is
428+
inefficient, error-prone and difficult to update because it mixes with
429+
application-specific code.
430+
431+
The solution is to use *ONBUILD* to register in advance instructions to run later,
432+
during the next build stage.
433+
434+
Here's how it works:
435+
436+
1. When it encounters an *ONBUILD* instruction, the builder adds a trigger to
437+
the metadata of the image being built.
438+
The instruction does not otherwise affect the current build.
439+
440+
2. At the end of the build, a list of all triggers is stored in the image manifest,
441+
under the key *OnBuild*. They can be inspected with *docker inspect*.
442+
443+
3. Later the image may be used as a base for a new build, using the *FROM* instruction.
444+
As part of processing the *FROM* instruction, the downstream builder looks for *ONBUILD*
445+
triggers, and executes them in the same order they were registered. If any of the
446+
triggers fail, the *FROM* instruction is aborted which in turn causes the build
447+
to fail. If all triggers succeed, the FROM instruction completes and the build
448+
continues as usual.
449+
450+
4. Triggers are cleared from the final image after being executed. In other words
451+
they are not inherited by "grand-children" builds.
452+
453+
For example you might add something like this:
454+
455+
.. code-block:: bash
456+
457+
[...]
458+
ONBUILD ADD . /app/src
459+
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
460+
[...]
461+
462+
405463
.. _dockerfile_examples:
406464

407465
4. Dockerfile Examples

integration/buildfile_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,3 +847,19 @@ func TestBuildFailsDockerfileEmpty(t *testing.T) {
847847
t.Fatal("Expected: %v, got: %v", docker.ErrDockerfileEmpty, err)
848848
}
849849
}
850+
851+
func TestBuildOnBuildTrigger(t *testing.T) {
852+
_, err := buildImage(testContextTemplate{`
853+
from {IMAGE}
854+
onbuild run echo here is the trigger
855+
onbuild run touch foobar
856+
`,
857+
nil, nil,
858+
},
859+
t, nil, true,
860+
)
861+
if err != nil {
862+
t.Fatal(err)
863+
}
864+
// FIXME: test that the 'foobar' file was created in the final build.
865+
}

0 commit comments

Comments
 (0)