From 8936789919c5c8004f346f44a3452d1521818b60 Mon Sep 17 00:00:00 2001
From: Josh Hawn <josh.hawn@docker.com>
Date: Tue, 28 Oct 2014 14:06:23 -0700
Subject: [PATCH] Make `FROM scratch` a special cased 'no-base' spec

There has been a lot of discussion (issues 4242 and 5262) about making
`FROM scratch` either a special case or making `FROM` optional, implying
starting from an empty file system.

This patch makes the build command `FROM scratch` special cased from now on
and if used does not pull/set the the initial layer of the build to the ancient
image ID (511136ea..) but instead marks the build as having no base image. The
next command in the dockerfile will create an image with a parent image ID of "".
This means every image ever can now use one fewer layer!

This also makes the image name `scratch` a reserved name by the TagStore. You
will not be able to tag an image with this name from now on. If any users
currently have an image tagged as `scratch`, they will still be able to use that
image, but will not be able to tag a new image with that name.

Goodbye '511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158',
it was nice knowing you.

Fixes #4242

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
---
 api/client/commands.go                       |  7 +++++-
 builder/dispatchers.go                       | 14 +++++++++++-
 builder/evaluator.go                         |  2 +-
 builder/internals.go                         |  4 ++--
 daemon/commit.go                             |  8 +++----
 daemon/container.go                          |  8 +++----
 daemon/create.go                             | 24 +++++++++++++-------
 daemon/daemon.go                             | 12 +++++-----
 daemon/image_delete.go                       |  2 +-
 daemon/inspect.go                            |  2 +-
 daemon/list.go                               |  2 +-
 graph/tags.go                                |  3 +++
 integration-cli/docker_cli_events_test.go    |  6 ++---
 integration-cli/docker_cli_inspect_test.go   |  2 +-
 integration-cli/docker_cli_pull_test.go      |  6 ++---
 integration-cli/docker_cli_save_load_test.go |  6 ++---
 project/make/.ensure-scratch                 |  4 ++--
 17 files changed, 70 insertions(+), 42 deletions(-)

diff --git a/api/client/commands.go b/api/client/commands.go
index 89e5796bbb..6290268b46 100644
--- a/api/client/commands.go
+++ b/api/client/commands.go
@@ -1696,7 +1696,12 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 
 		ports.ReadListFrom([]byte(out.Get("Ports")))
 
-		fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand,
+		image := out.Get("Image")
+		if image == "" {
+			image = "<no image>"
+		}
+
+		fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand,
 			units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))),
 			out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
 
diff --git a/builder/dispatchers.go b/builder/dispatchers.go
index 9fb44fc452..6108967c3b 100644
--- a/builder/dispatchers.go
+++ b/builder/dispatchers.go
@@ -21,6 +21,12 @@ import (
 	"github.com/docker/docker/runconfig"
 )
 
+const (
+	// NoBaseImageSpecifier is the symbol used by the FROM
+	// command to specify that no base image is to be used.
+	NoBaseImageSpecifier string = "scratch"
+)
+
 // dispatch with no layer / parsing. This is effectively not a command.
 func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
 	return nil
@@ -115,6 +121,12 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
 
 	name := args[0]
 
+	if name == NoBaseImageSpecifier {
+		b.image = ""
+		b.noBaseImage = true
+		return nil
+	}
+
 	image, err := b.Daemon.Repositories().LookupImage(name)
 	if b.Pull {
 		image, err = b.pullImage(name)
@@ -191,7 +203,7 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
 // RUN [ "echo", "hi" ] # echo hi
 //
 func run(b *Builder, args []string, attributes map[string]bool, original string) error {
-	if b.image == "" {
+	if b.image == "" && !b.noBaseImage {
 		return fmt.Errorf("Please provide a source image with `from` prior to run")
 	}
 
diff --git a/builder/evaluator.go b/builder/evaluator.go
index 4ed66c0054..eef222b943 100644
--- a/builder/evaluator.go
+++ b/builder/evaluator.go
@@ -110,7 +110,7 @@ type Builder struct {
 	cmdSet      bool          // indicates is CMD was set in current Dockerfile
 	context     tarsum.TarSum // the context is a tarball that is uploaded by the client
 	contextPath string        // the path of the temporary directory the local context is unpacked to (server side)
-
+	noBaseImage bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
 }
 
 // Run the builder with the context. This is the lynchpin of this package. This
diff --git a/builder/internals.go b/builder/internals.go
index d30a7da810..1caa33141c 100644
--- a/builder/internals.go
+++ b/builder/internals.go
@@ -58,7 +58,7 @@ func (b *Builder) readContext(context io.Reader) error {
 }
 
 func (b *Builder) commit(id string, autoCmd []string, comment string) error {
-	if b.image == "" {
+	if b.image == "" && !b.noBaseImage {
 		return fmt.Errorf("Please provide a source image with `from` prior to commit")
 	}
 	b.Config.Image = b.image
@@ -513,7 +513,7 @@ func (b *Builder) probeCache() (bool, error) {
 }
 
 func (b *Builder) create() (*daemon.Container, error) {
-	if b.image == "" {
+	if b.image == "" && !b.noBaseImage {
 		return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
 	}
 	b.Config.Image = b.image
diff --git a/daemon/commit.go b/daemon/commit.go
index 950925ade3..06d0465adc 100644
--- a/daemon/commit.go
+++ b/daemon/commit.go
@@ -59,17 +59,17 @@ func (daemon *Daemon) Commit(container *Container, repository, tag, comment, aut
 
 	// Create a new image from the container's base layers + a new layer from container changes
 	var (
-		containerID, containerImage string
-		containerConfig             *runconfig.Config
+		containerID, parentImageID string
+		containerConfig            *runconfig.Config
 	)
 
 	if container != nil {
 		containerID = container.ID
-		containerImage = container.Image
+		parentImageID = container.ImageID
 		containerConfig = container.Config
 	}
 
-	img, err := daemon.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config)
+	img, err := daemon.graph.Create(rwTar, containerID, parentImageID, comment, author, containerConfig, config)
 	if err != nil {
 		return nil, err
 	}
diff --git a/daemon/container.go b/daemon/container.go
index 3c05c645a7..75cd133fec 100644
--- a/daemon/container.go
+++ b/daemon/container.go
@@ -62,8 +62,8 @@ type Container struct {
 	Path string
 	Args []string
 
-	Config *runconfig.Config
-	Image  string
+	Config  *runconfig.Config
+	ImageID string `json:"Image"`
 
 	NetworkSettings *NetworkSettings
 
@@ -186,7 +186,7 @@ func (container *Container) WriteHostConfig() error {
 
 func (container *Container) LogEvent(action string) {
 	d := container.daemon
-	if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.Image)).Run(); err != nil {
+	if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.ImageID)).Run(); err != nil {
 		log.Errorf("Error logging event %s for %s: %s", action, container.ID, err)
 	}
 }
@@ -786,7 +786,7 @@ func (container *Container) GetImage() (*image.Image, error) {
 	if container.daemon == nil {
 		return nil, fmt.Errorf("Can't get image of unregistered container")
 	}
-	return container.daemon.graph.Get(container.Image)
+	return container.daemon.graph.Get(container.ImageID)
 }
 
 func (container *Container) Unmount() error {
diff --git a/daemon/create.go b/daemon/create.go
index f9d986491f..958a42ea73 100644
--- a/daemon/create.go
+++ b/daemon/create.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/docker/docker/engine"
 	"github.com/docker/docker/graph"
+	"github.com/docker/docker/image"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/libcontainer/label"
@@ -68,15 +69,22 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 	var (
 		container *Container
 		warnings  []string
+		img       *image.Image
+		imgID     string
+		err       error
 	)
 
-	img, err := daemon.repositories.LookupImage(config.Image)
-	if err != nil {
-		return nil, nil, err
-	}
-	if err := img.CheckDepth(); err != nil {
-		return nil, nil, err
+	if config.Image != "" {
+		img, err = daemon.repositories.LookupImage(config.Image)
+		if err != nil {
+			return nil, nil, err
+		}
+		if err = img.CheckDepth(); err != nil {
+			return nil, nil, err
+		}
+		imgID = img.ID
 	}
+
 	if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
 		return nil, nil, err
 	}
@@ -86,13 +94,13 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 			return nil, nil, err
 		}
 	}
-	if container, err = daemon.newContainer(name, config, img); err != nil {
+	if container, err = daemon.newContainer(name, config, imgID); err != nil {
 		return nil, nil, err
 	}
 	if err := daemon.Register(container); err != nil {
 		return nil, nil, err
 	}
-	if err := daemon.createRootfs(container, img); err != nil {
+	if err := daemon.createRootfs(container); err != nil {
 		return nil, nil, err
 	}
 	if hostConfig != nil {
diff --git a/daemon/daemon.go b/daemon/daemon.go
index 40b56ea1cf..1553f198d1 100644
--- a/daemon/daemon.go
+++ b/daemon/daemon.go
@@ -417,10 +417,10 @@ func (daemon *Daemon) checkDeprecatedExpose(config *runconfig.Config) bool {
 
 func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.Image) ([]string, error) {
 	warnings := []string{}
-	if daemon.checkDeprecatedExpose(img.Config) || daemon.checkDeprecatedExpose(config) {
+	if (img != nil && daemon.checkDeprecatedExpose(img.Config)) || daemon.checkDeprecatedExpose(config) {
 		warnings = append(warnings, "The mapping to public ports on your host via Dockerfile EXPOSE (host:port:port) has been deprecated. Use -p to publish the ports.")
 	}
-	if img.Config != nil {
+	if img != nil && img.Config != nil {
 		if err := runconfig.Merge(config, img.Config); err != nil {
 			return nil, err
 		}
@@ -557,7 +557,7 @@ func parseSecurityOpt(container *Container, config *runconfig.HostConfig) error
 	return err
 }
 
-func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) {
+func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID string) (*Container, error) {
 	var (
 		id  string
 		err error
@@ -578,7 +578,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
 		Args:            args, //FIXME: de-duplicate from config
 		Config:          config,
 		hostConfig:      &runconfig.HostConfig{},
-		Image:           img.ID, // Always use the resolved image id
+		ImageID:         imgID,
 		NetworkSettings: &NetworkSettings{},
 		Name:            name,
 		Driver:          daemon.driver.String(),
@@ -590,14 +590,14 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
 	return container, err
 }
 
-func (daemon *Daemon) createRootfs(container *Container, img *image.Image) error {
+func (daemon *Daemon) createRootfs(container *Container) error {
 	// Step 1: create the container directory.
 	// This doubles as a barrier to avoid race conditions.
 	if err := os.Mkdir(container.root, 0700); err != nil {
 		return err
 	}
 	initID := fmt.Sprintf("%s-init", container.ID)
-	if err := daemon.driver.Create(initID, img.ID); err != nil {
+	if err := daemon.driver.Create(initID, container.ImageID); err != nil {
 		return err
 	}
 	initPath, err := daemon.driver.Get(initID, "")
diff --git a/daemon/image_delete.go b/daemon/image_delete.go
index f39b0dd617..19f81f11a5 100644
--- a/daemon/image_delete.go
+++ b/daemon/image_delete.go
@@ -131,7 +131,7 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.
 
 func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
 	for _, container := range daemon.List() {
-		parent, err := daemon.Repositories().LookupImage(container.Image)
+		parent, err := daemon.Repositories().LookupImage(container.ImageID)
 		if err != nil {
 			if daemon.Graph().IsNotExist(err) {
 				return nil
diff --git a/daemon/inspect.go b/daemon/inspect.go
index c930cdd7fc..2bf1773d3a 100644
--- a/daemon/inspect.go
+++ b/daemon/inspect.go
@@ -35,7 +35,7 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status {
 		out.SetList("Args", container.Args)
 		out.SetJson("Config", container.Config)
 		out.SetJson("State", container.State)
-		out.SetJson("Image", container.Image)
+		out.Set("Image", container.ImageID)
 		out.SetJson("NetworkSettings", container.NetworkSettings)
 		out.Set("ResolvConfPath", container.ResolvConfPath)
 		out.Set("HostnamePath", container.HostnamePath)
diff --git a/daemon/list.go b/daemon/list.go
index 188a9861ec..937cdd2123 100644
--- a/daemon/list.go
+++ b/daemon/list.go
@@ -116,7 +116,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
 		out := &engine.Env{}
 		out.SetJson("Id", container.ID)
 		out.SetList("Names", names[container.ID])
-		out.SetJson("Image", daemon.Repositories().ImageName(container.Image))
+		out.SetJson("Image", daemon.Repositories().ImageName(container.ImageID))
 		if len(container.Args) > 0 {
 			args := []string{}
 			for _, arg := range container.Args {
diff --git a/graph/tags.go b/graph/tags.go
index 5c3e533b2a..826ab0bf75 100644
--- a/graph/tags.go
+++ b/graph/tags.go
@@ -298,6 +298,9 @@ func validateRepoName(name string) error {
 	if name == "" {
 		return fmt.Errorf("Repository name can't be empty")
 	}
+	if name == "scratch" {
+		return fmt.Errorf("'scratch' is a reserved name")
+	}
 	return nil
 }
 
diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go
index a56788e219..8f7263c0be 100644
--- a/integration-cli/docker_cli_events_test.go
+++ b/integration-cli/docker_cli_events_test.go
@@ -230,9 +230,9 @@ func TestEventsRedirectStdout(t *testing.T) {
 
 func TestEventsImagePull(t *testing.T) {
 	since := time.Now().Unix()
-	pullCmd := exec.Command(dockerBinary, "pull", "scratch")
+	pullCmd := exec.Command(dockerBinary, "pull", "hello-world")
 	if out, _, err := runCommandWithOutput(pullCmd); err != nil {
-		t.Fatalf("pulling the scratch image from has failed: %s, %v", out, err)
+		t.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err)
 	}
 
 	eventsCmd := exec.Command(dockerBinary, "events",
@@ -243,7 +243,7 @@ func TestEventsImagePull(t *testing.T) {
 	events := strings.Split(strings.TrimSpace(out), "\n")
 	event := strings.TrimSpace(events[len(events)-1])
 
-	if !strings.HasSuffix(event, "scratch:latest: pull") {
+	if !strings.HasSuffix(event, "hello-world:latest: pull") {
 		t.Fatalf("Missing pull event - got:%q", event)
 	}
 
diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go
index bb99818bf9..cf42217ac8 100644
--- a/integration-cli/docker_cli_inspect_test.go
+++ b/integration-cli/docker_cli_inspect_test.go
@@ -7,7 +7,7 @@ import (
 )
 
 func TestInspectImage(t *testing.T) {
-	imageTest := "scratch"
+	imageTest := "emptyfs"
 	imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"
 	imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Id}}'", imageTest)
 	out, exitCode, err := runCommandWithOutput(imagesCmd)
diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go
index b67b1caca5..5b3324c771 100644
--- a/integration-cli/docker_cli_pull_test.go
+++ b/integration-cli/docker_cli_pull_test.go
@@ -9,11 +9,11 @@ import (
 
 // pulling an image from the central registry should work
 func TestPullImageFromCentralRegistry(t *testing.T) {
-	pullCmd := exec.Command(dockerBinary, "pull", "scratch")
+	pullCmd := exec.Command(dockerBinary, "pull", "hello-world")
 	if out, _, err := runCommandWithOutput(pullCmd); err != nil {
-		t.Fatalf("pulling the scratch image from the registry has failed: %s, %v", out, err)
+		t.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err)
 	}
-	logDone("pull - pull scratch")
+	logDone("pull - pull hello-world")
 }
 
 // pulling a non-existing image from the central registry should return a non-zero exit code
diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go
index 94bfe3d6a8..c745df69f3 100644
--- a/integration-cli/docker_cli_save_load_test.go
+++ b/integration-cli/docker_cli_save_load_test.go
@@ -270,7 +270,7 @@ func TestSaveSingleTag(t *testing.T) {
 func TestSaveImageId(t *testing.T) {
 	repoName := "foobar-save-image-id-test"
 
-	tagCmdFinal := fmt.Sprintf("%v tag scratch:latest %v:latest", dockerBinary, repoName)
+	tagCmdFinal := fmt.Sprintf("%v tag emptyfs:latest %v:latest", dockerBinary, repoName)
 	tagCmd := exec.Command("bash", "-c", tagCmdFinal)
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
@@ -370,7 +370,7 @@ func TestSaveMultipleNames(t *testing.T) {
 	repoName := "foobar-save-multi-name-test"
 
 	// Make one image
-	tagCmdFinal := fmt.Sprintf("%v tag scratch:latest %v-one:latest", dockerBinary, repoName)
+	tagCmdFinal := fmt.Sprintf("%v tag emptyfs:latest %v-one:latest", dockerBinary, repoName)
 	tagCmd := exec.Command("bash", "-c", tagCmdFinal)
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
@@ -378,7 +378,7 @@ func TestSaveMultipleNames(t *testing.T) {
 	defer deleteImages(repoName + "-one")
 
 	// Make two images
-	tagCmdFinal = fmt.Sprintf("%v tag scratch:latest %v-two:latest", dockerBinary, repoName)
+	tagCmdFinal = fmt.Sprintf("%v tag emptyfs:latest %v-two:latest", dockerBinary, repoName)
 	tagCmd = exec.Command("bash", "-c", tagCmdFinal)
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
diff --git a/project/make/.ensure-scratch b/project/make/.ensure-scratch
index 9a9a43a0f8..8c421ed29e 100644
--- a/project/make/.ensure-scratch
+++ b/project/make/.ensure-scratch
@@ -1,13 +1,13 @@
 #!/bin/bash
 
 if ! docker inspect scratch &> /dev/null; then
-	# let's build a "docker save" tarball for "scratch"
+	# let's build a "docker save" tarball for "emptyfs"
 	# see https://github.com/docker/docker/pull/5262
 	# and also https://github.com/docker/docker/issues/4242
 	mkdir -p /docker-scratch
 	(
 		cd /docker-scratch
-		echo '{"scratch":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}' > repositories
+		echo '{"emptyfs":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}' > repositories
 		mkdir -p 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
 		(
 			cd 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158