Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker Image 'chown -R daemon:daemon' duplicates application layer #883

Closed
gavares opened this issue Sep 26, 2016 · 12 comments
Closed

Docker Image 'chown -R daemon:daemon' duplicates application layer #883

gavares opened this issue Sep 26, 2016 · 12 comments

Comments

@gavares
Copy link

gavares commented Sep 26, 2016

sbt-native-packager versions 1.2.0-M5 and 1.1.4

Affer adding application contents to a docker image, another layer is created with command:

chown -R daemon:daemon .

This touches every file in the application and creates new layer that has the same size as the application. In my case, after adding ~130mb layer containing my app, another 130mb layer is added as a result of touching every file in the my app layer.

docker history shows:
image

Wondering if this is necessary and/or if it could be done before adding the files from the application layer to the container.

@gavares
Copy link
Author

gavares commented Sep 26, 2016

Seems this is caused by a very annoying docker issue that has had years of discussions and github issues opened but still hasn't been fixed.

The current workaround is to perform chown/chmod locally, create a tar of the contents and then ADD the tar which docker will magically unpack, preserving permissions of the contents.

Not sure if there will be any issues getting this to work since a user (probably) won't have the daemon user and group locally on their dev machine and, even if they did, the uid/gid probably wouldn't match what is present in the container anyway.

@muuki88 muuki88 added the docker label Sep 28, 2016
@muuki88
Copy link
Contributor

muuki88 commented Sep 28, 2016

There have already been some discussions on gitter on this topic ( I couldn't find unfortunately ).

For docker we have some options to optimize this (e.g. splitting the app jars from the lib jars to enable caching). However this needs time, testing and verification. So if you have good solution with proper research we I'm happy to discuss this on a pull request from you :)

cc @fiadliel

@NeQuissimus
Copy link
Contributor

You'll want to follow moby/moby#30110.

If you don't have volumes, it should be reasonably safe to just remove the USER and chown from the build instructions and thereby save the space. That will, however, cause your applications to be root, albeit inside a container.

@fiadliel
Copy link
Contributor

Thanks, I'll keep an eye on it, an alternative is the "squash" command (when it becomes stable).

But I would disagree that it's safe to remove USER/chown - the proof lies in decades of escapes from chroots on Unix systems (and Docker containers are just chroots on steroids).

@NeQuissimus
Copy link
Contributor

Personally, I am using https://github.com/goldmann/docker-squash, which gets rid of the "root-owned files"-layer altogether.

@ivantopo
Copy link

ivantopo commented May 9, 2017

hey people, I did a quick and dirty hack to improve layer caching on our projects that basically copies the libraries in one layer and the project files in another layer and completely skips the chown.. I think that the chown could be less problematic if it was also separated in one command for the libs and one for the projects but didn't test it. See this comparison of new/old images:

image

before the change we would need to upload 2 layers of 44.4MB every time we do a release, now (if no dependencies change) we would only be uploading 412Kb 🎉

here is a gist with the changes: https://gist.github.com/ivantopo/8dbc581723458a1614be3c6c11ab90da

@muuki88 would you be interested in a PR?

@muuki88
Copy link
Contributor

muuki88 commented May 9, 2017

Thanks @ivantopo for sharing this 😍

I guess we have to start at some point and your gist was the best looking and working solution I have seen so far. As a first step I would like to add this as a separate archetype plugin, so people can turn it on if they feel brave enough. Also I would keep the chowing in the first place. Or introduce an setting dockerChownMappings: Setting key[Boolean] which triggers this behaviour , but only for the new plugin.

If you need any guidance, feel free to ask. Also don't hesitate to open a pull request early and we discuss details there .

Thanks again and looking forward to this 😎

@mosche
Copy link

mosche commented May 12, 2017

@muuki88 Just saw this issue. Here's another idea we ended up using.
https://gist.github.com/mosche/0a2ebf0966c449b1c48feb8e52dedd97

The idea is to map libraries and their transitive dependencies into multiple layers that can even be shared between different services :) The only downside: it's using sbt-dependency-graph to resolve dependencies. But the result is pretty amazing...

@muuki88
Copy link
Contributor

muuki88 commented May 12, 2017

Thanks @mosche for sharing :)

It looks interesting, but I guess would be to specific for native-packager. Creating shared docker images from a single build is something more special. I would also like to avoid a dependency on sbt-dependency-graph.

Splitting application and dependencies into two layers is a performance optimization ( that's really what it's all about ) which yields a hopefully small amount of complexity. And must be explicitly enabled. Creating multiple layers from regex and a 3rd party plugin, won't be maintainable with our limited resources.

Still this is pretty crazy what you can do with this :D

@NeQuissimus
Copy link
Contributor

Update: moby/moby#34263 has a fix for this now

@muuki88
Copy link
Contributor

muuki88 commented Aug 30, 2017

Thanks for sharing @NeQuissimus ❤️

@mrfyda
Copy link
Contributor

mrfyda commented Oct 24, 2017

@muuki88 I guess this was also fixed by #1044

@muuki88 muuki88 closed this as completed Nov 1, 2017
eed3si9n added a commit to eed3si9n/sbt-native-packager that referenced this issue Jan 18, 2019
Fixes sbt#1189

This implements a non-root Docker container that's safer by default and compatible with Red Hat OpenShift.
Current `ADD --chown=daemon:daemon opt /opt` nominally implements non-root image,
but by giving ownership of the working directory to the `daemon` user, it reduces the safety.
Instead we should keep the file ownership by Docker's `root`, and use `chmod` to grant access to `daemon`.

The challenge is calling `chmod` without incurring the fs layer overhead (sbt#883).
[Multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) can be used to pre-stage
the files with desired file permissions.

This adds new `dockerPermissionStrategy` setting which decides how file permissions are set for the working directory inside the Docker image generated by sbt-native-packager. The strategies are:

- `DockerPermissionStrategy.MultiStage` (default): uses multi-stage Docker build to call chmod ahead of time.
- `DockerPermissionStrategy.None`: does not attempt to change the file permissions, and use the host machine's file mode bits.
- `DockerPermissionStrategy.Run`: calls `RUN` in the Dockerfile. This has regression on the resulting Docker image file size.
- `DockerPermissionStrategy.CopyChown`: calls `COPY --chown` in the Dockerfile. Provided as a backward compatibility.

For `MultiStage` and `Run` strategies, `dockerChmodType` is used in addition to call `chmod` during Docker build.

- `DockerChmodType.UserGroupReadExecute`: chmod -R u=rX,g=rX
- `DockerChmodType.UserGroupWriteExecute`: chmod -R u=rwX,g=rwX
- `DockerChmodType.SyncGroupToUser`: chmod -R g=u
- `DockerChmodType.Custom`: Custom argument provided by the user.

Here's an example to change these settings:

```scala
import com.typesafe.sbt.packager.docker._

dockerPermissionStrategy := DockerPermissionStrategy.Run
dockerChmodType          := DockerChmodType.UserGroupWriteExecute
```

During `docker:stage`, Docker package validation is called to check if the selected strategy is compatible with the deteted Docker version.
This fixes the current repeatability issue reported as sbt#1187. If the incompatibility is detected, the user is advised to
either upgrade their Docker, pick another strategy, or override the `dockerVersion` setting.

`daemonGroup` is set to `root` instead of copying the value from the `daemonUser` setting.
This matches the semantics of `USER` as well as OpenShift, which uses gid=0.
eed3si9n added a commit to eed3si9n/sbt-native-packager that referenced this issue Jan 18, 2019
Fixes sbt#1189

This implements a non-root Docker container that's safer by default and compatible with Red Hat OpenShift.
Current `ADD --chown=daemon:daemon opt /opt` nominally implements non-root image,
but by giving ownership of the working directory to the `daemon` user, it reduces the safety.
Instead we should keep the file ownership by Docker's `root`, and use `chmod` to grant access to `daemon`.

The challenge is calling `chmod` without incurring the fs layer overhead (sbt#883).
[Multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) can be used to pre-stage
the files with desired file permissions.

This adds new `dockerPermissionStrategy` setting which decides how file permissions are set for the working directory inside the Docker image generated by sbt-native-packager. The strategies are:

- `DockerPermissionStrategy.MultiStage` (default): uses multi-stage Docker build to call chmod ahead of time.
- `DockerPermissionStrategy.None`: does not attempt to change the file permissions, and use the host machine's file mode bits.
- `DockerPermissionStrategy.Run`: calls `RUN` in the Dockerfile. This has regression on the resulting Docker image file size.
- `DockerPermissionStrategy.CopyChown`: calls `COPY --chown` in the Dockerfile. Provided as a backward compatibility.

For `MultiStage` and `Run` strategies, `dockerChmodType` is used in addition to call `chmod` during Docker build.

- `DockerChmodType.UserGroupReadExecute` (default): chmod -R u=rX,g=rX
- `DockerChmodType.UserGroupWriteExecute`: chmod -R u=rwX,g=rwX
- `DockerChmodType.SyncGroupToUser`: chmod -R g=u
- `DockerChmodType.Custom`: Custom argument provided by the user.

Some application will require writing files to the working directory.
In that case the setting should be changed as follows:

```scala
import com.typesafe.sbt.packager.docker.DockerChmodType

dockerChmodType := DockerChmodType.UserGroupWriteExecute
```

During `docker:stage`, Docker package validation is called to check if the selected strategy is compatible with the deteted Docker version.
This fixes the current repeatability issue reported as sbt#1187. If the incompatibility is detected, the user is advised to
either upgrade their Docker, pick another strategy, or override the `dockerVersion` setting.

`daemonGroup` is set to `root` instead of copying the value from the `daemonUser` setting.
This matches the semantics of `USER` as well as OpenShift, which uses gid=0.
eed3si9n added a commit to eed3si9n/sbt-native-packager that referenced this issue Jan 18, 2019
Fixes sbt#1189

This implements a non-root Docker container that's safer by default and compatible with Red Hat OpenShift.
Current `ADD --chown=daemon:daemon opt /opt` nominally implements non-root image,
but by giving ownership of the working directory to the `daemon` user, it reduces the safety.
Instead we should use `chmod` to default to read-only access unless the build user opts into writable working directory.

The challenge is calling `chmod` without incurring the fs layer overhead (sbt#883).
[Multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) can be used to pre-stage
the files with desired file permissions.

This adds new `dockerPermissionStrategy` setting which decides how file permissions are set for the working directory inside the Docker image generated by sbt-native-packager. The strategies are:

- `DockerPermissionStrategy.MultiStage` (default): uses multi-stage Docker build to call chmod ahead of time.
- `DockerPermissionStrategy.None`: does not attempt to change the file permissions, and use the host machine's file mode bits.
- `DockerPermissionStrategy.Run`: calls `RUN` in the Dockerfile. This has regression on the resulting Docker image file size.
- `DockerPermissionStrategy.CopyChown`: calls `COPY --chown` in the Dockerfile. Provided as a backward compatibility.

For `MultiStage` and `Run` strategies, `dockerChmodType` is used in addition to call `chmod` during Docker build.

- `DockerChmodType.UserGroupReadExecute` (default): chmod -R u=rX,g=rX
- `DockerChmodType.UserGroupWriteExecute`: chmod -R u=rwX,g=rwX
- `DockerChmodType.SyncGroupToUser`: chmod -R g=u
- `DockerChmodType.Custom`: Custom argument provided by the user.

Some application will require writing files to the working directory.
In that case the setting should be changed as follows:

```scala
import com.typesafe.sbt.packager.docker.DockerChmodType

dockerChmodType := DockerChmodType.UserGroupWriteExecute
```

During `docker:stage`, Docker package validation is called to check if the selected strategy is compatible with the deteted Docker version.
This fixes the current repeatability issue reported as sbt#1187. If the incompatibility is detected, the user is advised to
either upgrade their Docker, pick another strategy, or override the `dockerVersion` setting.

`daemonGroup` is set to `root` instead of copying the value from the `daemonUser` setting.
This matches the semantics of `USER` as well as OpenShift, which uses gid=0.
eed3si9n added a commit to eed3si9n/sbt-native-packager that referenced this issue Jan 18, 2019
Fixes sbt#1189

This implements a non-root Docker container that's safer by default and compatible with Red Hat OpenShift.
Current `ADD --chown=daemon:daemon opt /opt` nominally implements non-root image,
but by giving ownership of the working directory to the `daemon` user, it reduces the safety.
Instead we should use `chmod` to default to read-only access unless the build user opts into writable working directory.

The challenge is calling `chmod` without incurring the fs layer overhead (sbt#883).
[Multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) can be used to pre-stage
the files with desired file permissions.

This adds new `dockerPermissionStrategy` setting which decides how file permissions are set for the working directory inside the Docker image generated by sbt-native-packager. The strategies are:

- `DockerPermissionStrategy.MultiStage` (default): uses multi-stage Docker build to call chmod ahead of time.
- `DockerPermissionStrategy.None`: does not attempt to change the file permissions, and use the host machine's file mode bits.
- `DockerPermissionStrategy.Run`: calls `RUN` in the Dockerfile. This has regression on the resulting Docker image file size.
- `DockerPermissionStrategy.CopyChown`: calls `COPY --chown` in the Dockerfile. Provided as a backward compatibility.

For `MultiStage` and `Run` strategies, `dockerChmodType` is used in addition to call `chmod` during Docker build.

- `DockerChmodType.UserGroupReadExecute` (default): chmod -R u=rX,g=rX
- `DockerChmodType.UserGroupWriteExecute`: chmod -R u=rwX,g=rwX
- `DockerChmodType.SyncGroupToUser`: chmod -R g=u
- `DockerChmodType.Custom`: Custom argument provided by the user.

Some application will require writing files to the working directory.
In that case the setting should be changed as follows:

```scala
import com.typesafe.sbt.packager.docker.DockerChmodType

dockerChmodType := DockerChmodType.UserGroupWriteExecute
```

During `docker:stage`, Docker package validation is called to check if the selected strategy is compatible with the deteted Docker version.
This fixes the current repeatability issue reported as sbt#1187. If the incompatibility is detected, the user is advised to
either upgrade their Docker, pick another strategy, or override the `dockerVersion` setting.

`daemonGroup` is set to `root` instead of copying the value from the `daemonUser` setting.
This matches the semantics of `USER` as well as OpenShift, which uses gid=0.
eed3si9n added a commit to eed3si9n/sbt-native-packager that referenced this issue Jan 22, 2019
Fixes sbt#1189

This implements a non-root Docker container that's safer by default and compatible with Red Hat OpenShift.
Current `ADD --chown=daemon:daemon opt /opt` nominally implements non-root image,
but by giving ownership of the working directory to the `daemon` user, it reduces the safety.
Instead we should use `chmod` to default to read-only access unless the build user opts into writable working directory.

The challenge is calling `chmod` without incurring the fs layer overhead (sbt#883).
[Multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) can be used to pre-stage
the files with desired file permissions.

This adds new `dockerPermissionStrategy` setting which decides how file permissions are set for the working directory inside the Docker image generated by sbt-native-packager. The strategies are:

- `DockerPermissionStrategy.MultiStage` (default): uses multi-stage Docker build to call chmod ahead of time.
- `DockerPermissionStrategy.None`: does not attempt to change the file permissions, and use the host machine's file mode bits.
- `DockerPermissionStrategy.Run`: calls `RUN` in the Dockerfile. This has regression on the resulting Docker image file size.
- `DockerPermissionStrategy.CopyChown`: calls `COPY --chown` in the Dockerfile. Provided as a backward compatibility.

For `MultiStage` and `Run` strategies, `dockerChmodType` is used in addition to call `chmod` during Docker build.

- `DockerChmodType.UserGroupReadExecute` (default): chmod -R u=rX,g=rX
- `DockerChmodType.UserGroupWriteExecute`: chmod -R u=rwX,g=rwX
- `DockerChmodType.SyncGroupToUser`: chmod -R g=u
- `DockerChmodType.Custom`: Custom argument provided by the user.

Some application will require writing files to the working directory.
In that case the setting should be changed as follows:

```scala
import com.typesafe.sbt.packager.docker.DockerChmodType

dockerChmodType := DockerChmodType.UserGroupWriteExecute
```

During `docker:stage`, Docker package validation is called to check if the selected strategy is compatible with the deteted Docker version.
This fixes the current repeatability issue reported as sbt#1187. If the incompatibility is detected, the user is advised to
either upgrade their Docker, pick another strategy, or override the `dockerVersion` setting.

`daemonGroup` is set to `root` instead of copying the value from the `daemonUser` setting.
This matches the semantics of `USER` as well as OpenShift, which uses gid=0.
muuki88 pushed a commit that referenced this issue Jan 24, 2019
* Validate Docker packaging

* implement dockerPermissionStrategy

Fixes #1189

This implements a non-root Docker container that's safer by default and compatible with Red Hat OpenShift.
Current `ADD --chown=daemon:daemon opt /opt` nominally implements non-root image,
but by giving ownership of the working directory to the `daemon` user, it reduces the safety.
Instead we should use `chmod` to default to read-only access unless the build user opts into writable working directory.

The challenge is calling `chmod` without incurring the fs layer overhead (#883).
[Multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) can be used to pre-stage
the files with desired file permissions.

This adds new `dockerPermissionStrategy` setting which decides how file permissions are set for the working directory inside the Docker image generated by sbt-native-packager. The strategies are:

- `DockerPermissionStrategy.MultiStage` (default): uses multi-stage Docker build to call chmod ahead of time.
- `DockerPermissionStrategy.None`: does not attempt to change the file permissions, and use the host machine's file mode bits.
- `DockerPermissionStrategy.Run`: calls `RUN` in the Dockerfile. This has regression on the resulting Docker image file size.
- `DockerPermissionStrategy.CopyChown`: calls `COPY --chown` in the Dockerfile. Provided as a backward compatibility.

For `MultiStage` and `Run` strategies, `dockerChmodType` is used in addition to call `chmod` during Docker build.

- `DockerChmodType.UserGroupReadExecute` (default): chmod -R u=rX,g=rX
- `DockerChmodType.UserGroupWriteExecute`: chmod -R u=rwX,g=rwX
- `DockerChmodType.SyncGroupToUser`: chmod -R g=u
- `DockerChmodType.Custom`: Custom argument provided by the user.

Some application will require writing files to the working directory.
In that case the setting should be changed as follows:

```scala
import com.typesafe.sbt.packager.docker.DockerChmodType

dockerChmodType := DockerChmodType.UserGroupWriteExecute
```

During `docker:stage`, Docker package validation is called to check if the selected strategy is compatible with the deteted Docker version.
This fixes the current repeatability issue reported as #1187. If the incompatibility is detected, the user is advised to
either upgrade their Docker, pick another strategy, or override the `dockerVersion` setting.

`daemonGroup` is set to `root` instead of copying the value from the `daemonUser` setting.
This matches the semantics of `USER` as well as OpenShift, which uses gid=0.

* improve the names in file-permission scritped test

* add comment on globalSettings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants