Description
I've bumped into #34263 being merged and it hit me that --chmod
would be nice in conjunction with this feature.
I frequently build Linux images from a Windows machine and I'm kind of annoyed with the 755 mode the added/copied files end up with.
ADD/COPY with --chown
and --chmod
together will be a readable and maintainable notation delivering precise control over permissions.
I'm aware of #29853, but I don't think this issue is a duplicate (that one seems to be stale).
/cc @tonistiigi @AkihiroSuda @duglin
I got tons of Dockerfiles RUN
ning a chmod after a COPY
or ADD
, as I am not always confident in the execution rights of the files.
I'm personally ok with this; especially for the Windows case, I agree this makes a good addition. I'll bring this up in a maintainers meeting to get other opinions, but if a contributor wants to work on this, I think this would get accepted.
Discussing in the maintainers meeting, and we're okay with this proposal; we do need to design the syntax to be usable, and take into account that we probably want to have parity on Windows (both for --chown
and --chmod
); input / proposals for the would be welcome!
/cc @johnstep @tianon
w.r.t. Windows we can adopt Cygwin-like model:
--chown
rwx
bits-OR-
there can be Windows-specific syntax, e.g., we can specify security descriptor with SDDL
yes, such a notation is bulky and challenging, but it delivers precision and unambiguity
besides, people who manage file system permissions on Windows would better be aware of this syntax
This would indeed be a nice feature. A use case other than for Windows is when one wants to build a image that can be run as arbitrary non-root user via the docker --user
option. In this case, chmod
has to be used after COPY
to give group read/write permission (assuming group ID is set to 0 via chown
). This unfortunately invalidates the layer and makes the resulting image bigger.
@thaJeztah
I implemented the proposed chmod
flag: https://github.com/moby/moby/pull/36123
Waiting for it to be reviewed
I'm facing an issue with permissions; I'm ADD
ing a .tgz
file from an URL to an image, which is built FROM scratch
, after some inspection i found out once the file is ADD
ed the binary into it ends up with -rw-------
permissions.
Tried to build the image with a multi-stage Dockerfile, it ADD
s the .tgz
file, grants +x
permissions and then the binary is copied to the FROM scratch
stage, for some reason it shows exec format error
when running the resulting image.
@ulm0 Not 100%, but that sounds familiar, and I think it could have been that the binary is not staticly compiled. And since you're running FROM scratch
, the libraries it links against aren't present.
Here is a worked example from my own repo for doing it with golang: https://github.com/0xdevalias/docker-gobuster/blob/master/Dockerfile#L6
@0xdevalias the binary is built using musl, Copying the binary from a local folder works, but using ADD
breaks the image, I'm using another approach, this is the Dockerfile I'm using now so i can put this in a CI pipeline
FROM alpine:edge
RUN wget --quiet https://binaries.cockroachdb.com/cockroach-v1.1.6.linux-musl-amd64.tgz -O /tmp/cockroach.tgz && \
tar xvzf /tmp/cockroach.tgz --strip 1
FROM scratch
ENV COCKROACH_CHANNEL=official-docker
COPY ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=0 /cockroach /cockroach/cockroach
# This causes the binary within the tgz file to end up with 400 permissions in the /cockroach/ folder
# It throws a permission denied error when attempting to run
# ADD https://binaries.cockroachdb.com/cockroach-v1.1.6.linux-musl-amd64.tgz /cockroach/cockroach
WORKDIR /cockroach/
EXPOSE 26257 8080
ENTRYPOINT ["/cockroach/cockroach"]
Any update on this topic ?
This being postponed pending moby/buildkit#396. Basically there's major rewrite of the build engine going on. So the current engine is on feature freeze.
Will the caching behavior here be similar to how --chown
works? It looks to me after some local testing that the target ownership is part of the caching strategy, not the build context ownership.
For example:
Dockerfile
FROM alpine@sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
RUN addgroup -g 1500 delivery \
&& adduser -G delivery -D -u 2000 pizza
COPY --chown=pizza:delivery file.txt /tmp/chowned-file.txt
touch file.txt && chown mkobit:mkobit file.txt
docker image build -t "${PWD##*/}" --iidfile before.iidfile .
docker-copy-exploration > docker image build -t "${PWD##*/}" .
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM alpine@sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
---> 196d12cf6ab1
Step 2/3 : RUN addgroup -g 1500 delivery && adduser -G delivery -D -u 2000 pizza
---> Running in 4dac232d1f55
Removing intermediate container 4dac232d1f55
---> a9964e7c0f90
Step 3/3 : COPY --chown=pizza:delivery file.txt /tmp/chowned-file.txt
---> d92fedffdc8d
Successfully built d92fedffdc8d
Successfully tagged docker-copy-exploration:latest
chown docker:docker file.txt
docker image build -t "${PWD##*/}" --iidfile after.iidfile .
docker-copy-exploration > docker image build -t "${PWD##*/}" --iidfile after.iidfile .
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM alpine@sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
---> 196d12cf6ab1
Step 2/3 : RUN addgroup -g 1500 delivery && adduser -G delivery -D -u 2000 pizza
---> Using cache
---> a9964e7c0f90
Step 3/3 : COPY --chown=pizza:delivery file.txt /tmp/chowned-file.txt
---> Using cache
---> d92fedffdc8d
Successfully built d92fedffdc8d
Successfully tagged docker-copy-exploration:latest
compare image Ids (output above shows they are the same and caching the same, but just for completeness)
docker-copy-exploration > cmp --silent before.iidfile after.iidfile
docker-copy-exploration > echo $?
0
Will the same type of behavior be true for chmod
?
Who knows. This proposal is postponed. See ^^^.
But, actually, I don't see any issue. From docs:
All new files and directories are created with a UID and GID of 0, unless the optional --chown flag specifies a given username, groupname, or UID/GID combination to request specific ownership of the content added.
Given this, owner in the build context is absolutely irrelevant for build process.
Still, it requires in container available tooling to set UID and GID or use a ridiculous multi-stage build as described above. The feature freeze blocker is resolved - do we see some update on this now?
Any update on this topic ?
I want this.
@reporter123 seems like https://github.com/moby/buildkit/issues/396 is closed, can this get picked up again?
Any ETA?
This would be such a great move - it really makes sense to do this!
For sake of neutrality, I propose a platform-agnostic model
--owner=[uid/SID]
--executable=[true/false] (ignored in Windows containers as Win doesn't support the concept of executables)
--acls=[Unix chmod/Windows ACLS] (platform-dependent)
Most of the times, according to community, one needs just to ensure the file is executable. Using COPY --executable=true someFile.script destination.script
achieves chmod effect but has no additional effect on Windows. Should new platforms be made with different concepts of _executable_, the directive will be honored by Docker Engine.
If one needs fine grained control over chmod, they may use --acls
taking into account the effective platform. For Windows containers, it will enable Windows ACLs which are kind of complex compared to simple chmod
@djechelon Unfortunately, there are counter-arguments.
The syntax you propose adds three platform-dependent fields that are marketed as platform-agnostic.
I don't buy it, really.
--executable
is redundant because --acls
already specifies if file is to be executable (moreover, how to resolve conflicts between these two fields?)--owner
naming is also hard to sell as --chown
already exists and maps almost fine for Windows (almost because on Windows a security group can own objects)There is almost zero interest towards this feature, but after 2+ years of deliberation I tend to believe that file-system permissions are inherently not portable between Linux and Windows. It's not constructive to consider portability of inherently non-portable stuff a show-stopper.
What may work (almost repeating myself from September 2017):
--chown
and --chmod
in a straightforward way on LinuxThere is almost zero interest towards this feature
From who? I'd really like this feature.
I propose --chmod
syntax akin to rsync, which supports the usual chmod syntax plus you can restrict it to files or dirs like F440,D550
.
I code on Windows but I don't really care about Windows support, there's too many other problems using straight Windows; just use WSL2 and everything is mostly great.
There is almost zero interest towards this feature
From who? I'd really like this feature.
Zero interest from the side of the developers, who would have to actually sit and write a few lines of code.
@mnpenner rsync
syntax is a great and compatible refinement, great idea!
(Even though it's more for the sake of completeness rather than for the benefit of real use cases.)
@llech some time after me stumbling upon this issue, I've switched to fine-tuning in RPM rather than in Dockerfile. Multi-stage builds also address some extraneous layers issues. Doing chmod
not in Dockerfile but somewhere else actually enhanced my overall user experience.
Realistically, Dockerfile undoubtedly allows to do stuff beyond Hello World, but sometimes it's not the right tool for the job.
@mnpenner @djechelon Yes, the initial issue was because of building a Linux containers from Windows and being unable to make a script executable automagically based on permission bits in checkout. (Having to track executable bits for Git annoyed me some time ago until I wrote a script that does git update-index --chmod=+x
for any file with a shebang.)
But neglecting Windows is not an option. It's a robust and scalable system quite on par with Linux if handled properly. (Why it's almost never handled properly is not a politically correct topic as it upsets some incapable ops people.) There are features unique or better implemented in Linux and vice versa.
Windows Containers did bring some cash to Docker, Inc. so their wish not to introduce Linux-only features is kind of understandable.
On the other hand, the hype around Docker-based perception of containerization distorted Windows Server as a product and it's Docker that prevented rapid container adoption on Windows. If I'm not clear enough, think about this: there is no viable LXC-style solution for Windows whereas ops people on Windows side are kind of predisposed to system containers, not app containers.
Parallels/Virtuozzo Containers for Windows used to fill the niche, but Microsoft made it impossible to carry this solution beyond Windows Server 2012. (I concur with Microsoft in their reasoning, but I'm sad about it anyway. Full disclosure: I'm a former Parallels/@Virtuozzo developer.)
Not a good place to rant about Windows, but, like I said, the issue is Windows-related.
it's Docker that prevented rapid container adoption on Windows
This blog post from 2016 begs to differ: https://www.docker.com/blog/docker-microsoft-partnership/. Also, that rant was off topic maybe?
@sirlatrom Are you offended? Oh, you're a Docker Captain, I completely feel for you.
Yet, do you understand that your link not only doesn't contradict my point, but illustrates it?
A person familiar and involved with the subject in circa 2015--2017 would remember that initially (in Windows Server Technical Previews) there were system containers even capable of running Remote Desktop sessions containerized. I.e. the design used to be LXC-like. Then the mentioned Docker/Microsoft partnership had been struck and suddenly Windows Containers were stripped down to bare bones minimally required to support Docker application container vision. Coincidence, of course.
A great deal of Win32 and COM APIs interact with core user-space services, but e.g. LanmanServer
is crippled and disabled and LanmanWorkstation
is not fully functional.
As a result, there is no practical lift-and-shift for existing Windows workloads, whereas re-engineering a, say, .NET application toward .NET Core creates an opportunity to embrace Linux instead of Windows.
Plus, the concept of ephemeral containers still doesn't fit the way ops guys operate in real world. I do fail to comprehend a win-win scenario here.
(Full disclosure: once upon a time I've assessed feasibility of containerizing a proprietary ASP.NET application. I don't argue that the application was a model one, but it's definitely several order of magnitude more complicated/useful than 'Hello World'-style apps used in lift-and-shift marketing demos.)
Yet, the Docker support for Windows seems to haunt this proposal.
Phew! What is really off topic:
--chown
.chmod(2)
. A user-friendly convention/syntax for Dockerfile could be based onAnd for hardware producers and nuclear plant operators it's a blessing, because you need to buy more.
This is a proposal thread and it implies discussion. Following up emerging arguments is fine. So is disproving other people's points.
FWIW, I'm using PhpStorm with a project mounted under \\wsl$
and the performance seems reasonable. They have a ticket to add better support, so hopefully they will or MS will keep improving the interop.
I'm not familiar with this SDDL but sounds alright. Maybe Docker can add --chmod
and --sddl
as part of a separate change? I don't think these flags need to be cross-OS anyway, everything you put in the Dockerfile already depends on the container OS, no?
Really strange that this feature isn't already implemented!
Hello..
Supplementary argument for this feature request :
The only way of changing permissions after COPY commands is to RUN chmod -R .....
on the copied files.
This adds a new layer that has the same size as the copied files which practically double the final image size !
Doing some permission management is required for some usage like running Docker images in Openshift (Kubernetes) : https://docs.openshift.com/container-platform/3.3/creating_images/guidelines.html#use-uid
This cost us terabytes of disk for nothing.
Thanks
Another more generic option is running a command without creating a new layer (auto squashing with the previous layer). Then we don't really need all these hacky flags.
Another more generic option is running a command without creating a new layer (auto squashing with the previous layer). Then we don't really need all these hacky flags.
Try that on a scratch image.
I'm also running into this where some files do not have the right permissions and we can't change it during the COPY command, and have no choice but to add a second RUN command and double the image size. Hard to believe this was first brought up in 2017.
Try that on a scratch image.
Nonetheless, @bundabrg's suggestion would be helpful for tons of people.
If COPY --chown is supported, so should be --chmod
As a workaround for people who value image size (since there obviously isn't any traction on this issue), you can use multi-stage builds to add and "pre-chmod" your files in a temp image and then use COPY
to copy them to the final image, permissions and all.
Tested on Docker CE 19.03.5 on Linux. I don't use Docker for Windows so I can't confirm if it works there.
For example with the following Dockerfile
:
# Use an image with `chmod`
FROM alpine
ARG DUPLICACY_URL="https://github.com/gilbertchen/duplicacy/releases/download/v2.5.1/duplicacy_linux_x64_2.5.1"
# Add and chmod any files you want
ADD $DUPLICACY_URL /duplicacy
RUN chmod +rx /duplicacy
# Final image goes here
FROM alpine
# Copy from above temp image to final location
COPY --from=0 /duplicacy /usr/local/bin/duplicacy
ENTRYPOINT ["duplicacy"]
Results in:
Compared with the ADD/COPY
+ RUN chmod
pattern:
```
Final image goes here
FROM alpine
```
Assuming you mean FROM scratch
here?
# Final image goes here FROM alpine
Assuming you mean
FROM scratch
here?
Up to you. In my case I wanted alpine
in my final image.
In my case I wanted alpine in my final image.
That's literally the same as
FROM alpine
RUN set -eux; \
wget -qO /usr/local/bin/duplicacy https://github.com/gilbertchen/duplicacy/releases/download/v2.5.1/duplicacy_linux_x64_2.5.1; \
chmod +rx /usr/local/bin/duplicacy
ENTRYPOINT ["duplicacy"]
However, this is a perfect example why we need --chmod
for FROM scratch
images.
After all this discussion, I want to try to change my perspective.
Today, the --squash
option is just experimental. I think that it should become the de facto standard in Docker images.
Squashed images present the smallest size compared with any other layered image.
Rather than smashing our heads about additional copy options, platform-neutral ACLs and what is the correct way to handle scratch images, falling "back" to squash everything could be more helpful.
You can just run both commands and have the final image squashed into a single layer.
Squash allows to make an image consist of a very single layer on top of its base image.
I don't know about a single Docker Hub image benefitting from any single intermediate image.
A number of open packages install software using apk
or apt-get
. But really, those intermediate images don't necessarily add value. What you want when you download an image is the final image.
If it's correctly engineered over an existing base image (e.g. a simple Alpine over a more complex LAMP stack) you will really benefit from the tagged intermediate images
Example: Tomcat is made on top of Alpine, and what the image needs to contain is just Tomcat. To install Tomcat, the authors install wget and pgp on top of Alpine, but no "Alpine with wget and pgp" image is available.
And for the Tomcat example, there are a number of flavours (8.5, 9.0... based on JDK 8 or 11, based on Alpine or a larger image...), so layered images do not provide any additional benefit. You still need to pull different base images.
My 0.000000000002 cents
I guess multilayer would still be important for storage efficiency ?
@pptime Multilayer storage is efficient when you reuse (intermediate) layers. I know about reuse of images (final layers) than intermediate.
Let's take a look from another angle: developer productivity.
All the dirty workarounds proposed do create additional storage layers, yet they're eliminated by the time it comes to final distribution.
But all of them still require some additional amount of I/O during build and developers might do a lot of rebuilds daily, depending on whether a developer uses Docker for real work and not for packaging only.
Implementing --chmod
should be simple. Yet, it's still not done.
I believe, further discussion is counter-productive and futile.
Please, take a look at different container build tools or implement your own.
There exists #36123 and there's also some similar, yet much shorter drama in the comments. If you consider forking Docker for your own needs, this is a starting point.
As for my grievances about Docker (which extend far beyond this single proposal), at the moment I don't use Docker extensively, so I personally opt to do nothing.
Yes I agree, we use Docker as part of our continuous integration pipelines, and executing a supplementary chmod -R
on big directory structures is also I/O and time consuming.
Some graphdrivers don't create this kind of excessive I/O: DM and ZFS, probably Btrfs also.
There are, however, a whole host of difficulties and inconveniences with them.
As for overlay2
(the current ubiquitous default), the issue may be partially mitigated in recent Linux kernels, see this PR merged around August 2018.
Yet, I'm not sure if this really effectively accelerates chmod -R
by metadata-only copy-up.
And this overlay trickery definitely doesn't eliminate extra layers and doesn't make images slimmer when pushed to a registry.
On the other hand, most people use Ubuntu 18.04 an 16.04 on build hosts and are unlikely to jump to 20.04 soon. As for RHEL/CentOS world, EL8 kernel contains at least some of this functionality back-ported -- as it's not mentioned in the docs directly, it may work and it may not.
Still, a simple flag would be much more productive than delving into the kernel stuff.
In my case I wanted alpine in my final image.
That's literally the same as
FROM alpine RUN set -eux; \ wget -qO /usr/local/bin/duplicacy https://github.com/gilbertchen/duplicacy/releases/download/v2.5.1/duplicacy_linux_x64_2.5.1; \ chmod +rx /usr/local/bin/duplicacy ENTRYPOINT ["duplicacy"]
Sure, but it is just an example to demonstrate the workaround. That just happened to be one of the simpler Dockerfiles I had on hand to modify for an example.
Regardless of how unrealistic my example is, it still demonstrates a pattern that works for both remote and local files using only standard Dockerfile constructs. Whereas yours only works for remote files and also requires some knowledge of an external tool (ie wget
, curl
, etc). I don't see what you get out of nitpicking.
Can we add a new flag like --postcmd="chmod +x xxx". It let the user to do what they want.
In my situation, I'd like to setcap to the final target. Currently, I can only use RUN to add another layer to my image. If I have a postcmd in COPY, I can change these caps in one layer.
example:
FROM xxx
COPY --postcmd="setcap 'CAP_NET_BIND_SERVICE,CAP_NET_ADMIN,CAP_SYS_NICE,CAP_SYS_RESOURCE+eip' /example.exe" /path/to/target /example.exe
I'd be happy to prototype an implementation if you guys think it is ok.
A more generic and useful idea is "explicit transaction control", i.e. layer boundaries are user-defined, so that a layer may contain side effects of multiple file-system operations. I wouldn't dare propose wording, though. Plus such a ~transgression~ radical deviation from initial simplistic Dockerfile design warrants creation of an independent build tool. It shouldn't be hard given docker cp
, docker exec
and docker commit
already exist and do the hard stuff.
Otherwise, existing workarounds with multi-stage builds and squash exhibit similar "explicit transaction control" behavior and can be used instead.
As for setcap
use case, I seriously doubt it's a great idea to start with. Docker doesn't support a notion of multiple capability sets (i.e. permitted, inheritable, effective, bounding and ambient) and container's PID1 starts with all the default capabilities plus ones added with --cap-add
minus ones dropped with --cap-drop
. Adding xattrs to executable binaries with setcap
won't add effective capabilities unless they're already present in permitted set, and most likely the desired capabilities are already in place through inheritable set (unless dropped by a serious systemd-grade software, which is not welcome in Docker containers).
Since you need to explicitly define capability set on container creation, there is no need for setcap
magic (unless you use "systemd-grade software", but it's a completely different story).
@unshare Thank you for your reply.
I have made a little change in buildkit to support --chmod option, and my colleague said he have a setcap use case, ask me to support it. I have little known about capabilities in docker, so I think setcap is Linux based command, maybe it 's better to let user to decide what to do with the copied file.
I know what you mean, I'll discuss with my colleague about his use case with setcap, thank you.
As I dig into this problem, I found it's really very interesting.
In our case, we are trying to run our program in docker with non-root user, but our program needs some capabilities such as SYS_NICE. If we don't set the file-cap, we can not enable these capabilities because docker sets pcaps +i instead of +eip in non-root user. I found a better way is to use the ambient capabilities, but right now, docker doesn't support ambient capabilities. I read the issues about ambient capabilities supported by docker. Maybe it's because of the suid problem that blocks this feature?
So, I really don't know what's the better way to do in our use case. (Sorry this is really not related to this proposal)
Implemented in https://github.com/moby/buildkit/pull/1492
Sorry, I don't really understand the relationship between moby/buildkit and moby/moby and docker
as documented on https://docs.docker.com/engine/reference/builder/#add. Is there an ETA on when this syntax will be available from dockerfiles using the docker
command?
Docker v20 with this will be released within a few months
I'm using this feature as we speak, but I had to do some (not much) legwork beforehan and it was worth it.
BuildKit is a new build system that is mind-blowingly ahead of the regular Docker builder (which is feature-frozen for several years).
You can use BuildKit standalone, but it's also integrated with Docker for quite some time (you can replace the plain old builder with BuildKit by setting DOCKER_BUILDKIT=1
environment variable).
Or you can use buildx
the Docker CLI plugin (version included in Docker Desktop lags behind, so check out releases from GitHub).
To enable --chmod
right now, you need to explicitly specify Dockerfile parsing frontend (BuildKit has pluggable frontends).
The feature is already merged into master
, so you may use docker/dockerfile-upstream
repository from Docker (I believe, it's their CI output).
Add the following parser directive into the very top of your Dockerfile to activate (example):
syntax = docker/dockerfile-upstream:master-experimental
Of course, use it on your own risk, yada, yada.
FYI, I'm the original requestor and right now I'm more than happy with the execution on BuildKit side.
PS. Come on, BuildKit is not yet well-documented, but docs contain enough pointers to figure out everything I've just laid out and much more.
I can confirm that works great with BuildKit enable using env var DOCKER_BUILDKIT=1
and directive syntax = docker/dockerfile-upstream:master-experimental
with the octal syntax of chmod eg: --chmod=744
.
But it fails with classical alpha syntax --chmod=+x
, --chmod="+x"
or --chmod=o+x
and --chmod="o+x"
...
Am I missing something or may be it's not supported yet ?
@finalspy The alpha syntax is for changing the permissions on an existing file (e.g. o+x
means to set the execute bit to the "other" octet, leaving all other bits intact). What baseline permissions would you be changing from?
I could see an argument for supporting something like rwxr--r--
(equivalent of 744), but I don't think o+x
offers clear enough semantics for a good implementation.
@jakerobb it's "other", not "owner".
You have a point regarging +
and -
, but what about e.g. a=rwx
which is equal to u=rwx,g=rwx,o=rwx
, which is same as 777
?
@selurvedu thanks; corrected. I don't often use that syntax.
I was not familiar with the =
option. That seems supportable.
The usefulness of alpha syntax usually comes from the capital X
permission, which means "executable only if the file was previously executable or if it is a directory."
This is because a recursive COPY with --chmod=644
would make all directories non-executable, meaning non-traversable; while a --chmod=755
would make all files executable. Both are inappropriate therefore unuseable 99% of the time.
A recursive COPY with --chmod=u=rwX,go=rX
(capital Xes) would set directories to 755 and files to 644, unless a given file already had the executable bit in the source filesystem, in which case it would get 755.
Personally, I think both syntaxes are cumbersome. I'd rather have a --umask
setting, which is traditionally used to take away permissions., or maybe a UMASK top-level setting.
For example, --umask=022
would mean "not writable by group and others." This has the benefit of leaving the issue of executable files and/or traversable directories to Docker to figure out. Accordingly, --umask=077
would take away all rights except to the owner; --umask=0
would give everyone any available permissions; and so on.
This would probably be easier to adapt to Windows permissions, because it's a simpler specification.
Most helpful comment
Any update on this topic ?