Docker with make: build image on Dockerfile change

2020-05-28 02:08发布

问题:

I playing with Docker and make utility and try to write rule which rebuilds docker image only on Dockerfile change.
My project structure looks like:

 tree .
.
├── Dockerfile
├── Makefile
└── project
    └── 1.js

My Dockerfile is pretty simple:

FROM ubuntu

RUN apt-get update
RUN apt-get install -y curl 
RUN curl -sL https://deb.nodesource.com/setup | sudo bash -
RUN apt-get update
RUN apt-get install -y build-essential nodejs
VOLUME ["/project"]
ENTRYPOINT ["cat"]
CMD ["project/1.js"]

It just creates simple ubuntu image with nodejs installation and run a script from shared directory.

Now I want to run this image from Makefile. When I change a Dockerfile I want to rebuild the image. Makefile looks like:

default: run 

run: build
        docker run -v $(CURDIR)/project:/project app-server 

build: Dockerfile
        docker build -t app-server .

Now when I execute sudo make command it rebuild an image every time.

How can I force make to execute build task only when Dockerfile changed?

回答1:

When you write:

run: build
        docker run -v $(CURDIR)/project:/project app-server 

in a makefile make expects that that recipe will create a file by the name of run. make will then check that file's timestamp against the timestamp of its prerequisite files to determine if the recipe needs to be run the next time.

Similarly with the build target you have in your makefile.

build: Dockerfile
        docker build -t app-server .

Neither of those recipes create files with the name of the target however. This means that make cannot use the timestamp of that file to determine whether it needs to re-run the recipe. As such make has to assume that it needs to re-run the recipe (because assuming otherwise would mean the rule would never run).

If you run make -rRd you will see what make thinks is going on and you should see indication of what I've just said.

The solution to your problem, therefore, is to create stamp files in each of those targets.

Simply adding touch $@ (optionally prefixed with @ to silence the default make echoing of commands it runs) to each of those targets should be enough to get this to work for you.

That being said it might make sense to put sudo on each of the recipe lines that need it instead of running make with sudo if you don't want the stamp files to be owned as root as well.

For the record this is discussed in the GNU Make Manual as section 4.8 Empty Target Files to Record Events.



回答2:

Your "targets" default run and build are "phony" targets. That is an abstract concept, not a real file. Such phony targets, should not have a recipe (because you can't make them). They should instead depend on real files, or perhaps other phony targets, and so on, but everything must eventually depend on real files only.

Your phony targets should be marked as such

.PHONY: default run build

The real targets, on the other hand, should have a recipe - the recipe makes that target.

So, first depend your phony targets, without a recipe, on a real target(s).

Then have real targets have recipes.

I have posted some guidelines at makefile enforce library dependency ordering