Remove Docker VOLUME instruction from Dockerfile for more flexibility #2589
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This is branched off
master
, hopefully that's right (didn't see anything else in the README).I noticed this running v0.104.3 via Docker. The scenario this happened in is a bit contrived, but I think this is still a change that's useful for all sorts of use cases.
Expected behaviour
The expected behaviour I'm trying to achieve with this PR is:
Inheriting
FROM
adguard/adguardhome
and then baking files into/opt/adguardhome/conf
in a derivedDockerfile
should work.Actual behaviour
However, it doesn't work that way, because the base image declares volumes at two points, one of which is
/opt/adguardhome/conf
:AdGuardHome/scripts/make/Dockerfile
Line 38 in 7fab31b
As such, any e.g.
COPY
instructions will get overriden once the container is run. For example, take this Dockerfile:I think the
echo
s and comments sufficiently explain the weird and potentially confusing behaviour this has. This is a good compilation of reasons not to use theVOLUME
instruction in Dockerfiles (in fact some people say to removeVOLUME
entirely). Note that runningCOPY
into a directory, then declaring it as aVOLUME
afterwards works. But this is not available to child images. In theDockerfile
reference, it says:The worst and essentially breaking part here is that you cannot reset any of these properties in child images, see moby/moby/issues/3465 (7 year old topic...). If that were possible, I wouldn't need and have made this PR.
In the primary guide for running AdGuard Home in Docker, you use
--volume
instructions anyway. This renders theVOLUME
instructions void, as far as I can see. To persist the working directory, you wouldn't even need the--volume
option, since that is aVOLUME
. Docker would create an anonymous volume and data would be persisted. But it's better to be explicit and use--volume
imho, which you do -- but then,VOLUME
can just be discarded. This would allow for more flexibility for advanced use cases.Use case
The reason I even stumbled over this is trying a setup with Ansible. A controlling machine copies over the relevant files, like
Dockerfile
,docker-compose.yml
,AdGuardHome.yaml
etc. Those then get built and run by Ansible as well, using itsdocker_compose
module. This is very straightforward for basically all stuff out there, except for DNS servers!As stated in the wiki, we have to stop the container before changing the config files:
I tried without stopping the container first, can confirm it doesn't work. So the order goes, from the Ansible play:
dig
to succeed)This is stupid and confusing. I also want to update the image in the process, but in step 3, we have to do a simple
docker-compose up
, no pulling, since no DNS is up at that point.However, the much graver issue is that steps 2 and 3 can fail. You will be left naked, without a DNS server running (assuming it's the only one). I also had no DHCP server at that point. Ugly situation.
This wouldn't happen if instead of manipulating the "live" config files that get mounted into the container were baked into the image in the first place. From Ansible, it is very easy and not much of a difference to just build a new image every time, using a Dockerfile similar to:
This occurred to me and is a very simple solution to implement. It solves all of the above problems and simplifies the process to:
docker-compose up --build
)This updates the service and there is never any dangerous downtime. If the pull fails, the Dockerfile is invalid etc. etc., Docker will simply not recreate the service and leave the current one running. If everything builds and updates fine, it quickly recreates the service. Basically no downtime, and essentially no risk of ending up with no DNS server (except for when the new config files are somehow spectacularly corrupted).
Of course, this doesn't work because we
COPY
intoVOLUME
. The aboveCOPY
just silently fails. The new YAML config is never put into place, and upon container startup, the hidden, anonymous Docker volume is used again. This can lead to very surprising behaviour.For reference, I have a named volume for the working directory, and had planned on simply not using a volume for the
conf
directory, thinking I was smart. But Docker creates and uses one anyway:Conclusion
All guides seem to use explicit
--volume
instructions anway. These do not benefit or even use the base image'sVOLUME
instruction, they override it. Beginners will follow the guide and more advanced users will know to declare volumes, and also refer to the guide to see where to mount to. Even if you had noVOLUME
instruction and forgot to use--volume
, there isn't much to be lost if you lose your AGH's state.Advanced use cases that require inheriting from the base image are however hampered, and irretrievably so, since you cannot undo
VOLUME
instructions.I therefore see this change as a net gain with very little possibility of effing up.
We really rely on this being done upstream. As an alternative I looked into just taking the current Dockerfile verbatim, removing the
VOLUME
line and building. But that Dockerfile isn't a multi-stage file that compiles the binary itself, it just copies it in. So the Dockerfile is very heavily coupled to the surrounding CI (good stuff though!) and end users cannot easily take and modify it. Our best bet is to inherit from the built base image.Maybe a similar point could be made about the
EXPOSE
instruction, see also OxalisCommunity/oxalis/issues/440. They call it Dockerfile generalization, which is very fitting.