-
-
Notifications
You must be signed in to change notification settings - Fork 14.2k
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
Composable docker images with Nix #11156
Conversation
This is neat. Do you think it would be possible someday to have the layering work within the nix expressions? Could you have Also it sounds like with some tuning (figure out why gcc is pulled in) Nix could create the leanest container images of any OS since exactly the minimal set of dependencies for any given program would be pulled in. That would be almost micro-kernel like. |
@trishume I'm thinking about that and I feel that it may be possible. And yes, docker images with nix, if packages were properly optimized, would deliver the smallest images. |
@lethalman That 184 MB for Python sounds strange. I've been getting 36 MB tar.gz and 108.9 MB unpacked "virtual size" already. Since I've still learning Nix as language, I've simply:
Also good to know: If there's no explicit PATH env defined, Docker run seems to add the normal /usr/local/bin:/usr/bin:/bin/etc... so that Then there's that issue of many software expecting /bin/sh or /bin/bash and writable /tmp to exist. And usually one need to add many derivations to PATH. I went far enough to map expression build result into image root with:
And with buildEnv that results with /bin et al. having all the expected paths. Probably there are also downsides. |
@datakurre that's not only python, but also tools for extending the image later with layers (like curl, iproute, ...). Everything can be done, this is a PoC that nix images can compose with layers. |
@trishume so it seems possible to do with Also other the limitation is that kvm is needed in order to do the merge. Will do a complete PR. |
@lethalman That explains. You figured out quite a way to get data into image through the filter without ADD or COPY :) Still, could there be a way to create delta.tar.gz outside Docker (e.g. getting the paths from the base image with docker save) and use a Dockerfile with
|
@datakurre with a delta it could be possible yes, but it's still another step to do from the outside :( But yeah, it's indeed a step not involving an http server :D So I think all this will be divided into two things:
This leads to a series of image manipulation tools:
The above ones should be the very basic functionalities on top of which one creates other utility functions. |
@datakurre can you explain better? I didn't understand the image version with Nix profiles... |
@lethalman Sorry, I missed your update on deltas. Is there any option, which would work within Docker? (To make it possible to build a Linux Docker image on a Mac with boot2docker. E.g. some way to pass paths known to exist in baseImage so that there's no need to read them from the image with Docker.) About the profiles. I was thinking, if it would make sense to manage named dockered apps using Nix profiles so that it would be possible to create a delta tarball by diffing paths from the previous profile version to the current version. But I didn't think this too much. Probably something, where a bash script works better than a nix expression. Not suitable for hydra and very impure. |
@datakurre the problem is I didn't understand you first question... |
@lethalman Agreed. I understand this now. Also the first question was probably a misunderstanding from me. So, would |
So if you have an x86 VM, and you create two docker images from there, you can merge the result of course regardless of the architecture. Basically that |
c00d1aa
to
ddc776d
Compare
Alright :D We have it... with import <nixpkgs> {};
let
image1 = dockerTools.createImage { drv = bashInteractive; };
image2 = dockerTools.createImage { drv = python; };
in
dockerTools.mergeImages { name = "bash-python"; inherit image1 image2; }
There are three problems:
|
@lethalman I fell in love with you 😆 That's so great! I have to test it |
@geerds don't scare him away ✌️ |
Just reported the |
👍 |
ddc776d
to
b14a206
Compare
Alright. For now I've workarounded that issue, and we can only deal with layered images that have a name, because we cannot know easily the image id of the loaded image. So we can stack as many layers as we want from independent images: with import <nixpkgs> {};
rec {
image1 = dockerTools.createImage { drv = bash; };
image2 = dockerTools.createImage { drv = python; };
image3 = dockerTools.createImage { drv = perl; };
bash-python = dockerTools.mergeImages { name = "bash-python"; baseImage = image1; image = image2; };
bash-python-perl = dockerTools.mergeImages { name = "bash-python-perl"; baseImage = bash-python; image = image3; };
} Do Going to add also some other useful tools, like converting a flattened image to an image with one layer, and viceversa. |
@edolstra do you feel we can merge this? It's just tools, no harm for the build farm. |
Docker is taking up so much disk space (like over 2gb...) that I'm thinking of implementing the packing format of the images by myself. It's quite simple, and there will be no need of https://github.com/docker/docker/blob/master/image/spec/v1.md And they even reimplemented nix-hash: https://github.com/docker/docker/blob/master/pkg/tarsum/tarsum_spec.md |
b14a206
to
d7de35b
Compare
There we go, cleaned up of all the docker impure evilness. It's all pure nix without virtual machines, and without docker. We generate compatible docker images. with import ./. {};
rec {
image1 = dockerTools.mkImage { drv = bash; };
layeredImage1 = dockerTools.toLayeredImage { name = "bash"; image = image1; };
image2 = dockerTools.mkImage { drv = python; };
image3 = dockerTools.mkImage { drv = perl; };
bash-python = dockerTools.mergeImages { name = "bash-python"; baseImage = layeredImage1; image = image2; };
bash-python-perl = dockerTools.mergeImages { name = "bash-python-perl"; baseImage = bash-python; image = image3; };
}
Not only, we can also add all the various That Just one nitpick :D @edolstra why was I able to use |
46bc46a
to
75f24f1
Compare
New syntax: with import <nixpkgs> {};
with dockerTools;
rec {
image1 = mkTarball { drv = bash; };
layeredImage1 = build { name = "bash"; addTarball = image1; };
image2 = mkTarball { drv = python; };
image3 = mkTarball { drv = perl; };
bash-python = build { name = "bash-python"; fromImage = layeredImage1; addTarball = image2; };
bash-python-perl = build { name = "bash-python-perl"; fromImage = bash-python; addTarball = image3; };
just-python = build { name = "just-python"; config = { Cmd = [ "${python}/bin/${python.executable}" ]; }; };
} But now we also support arbitrary config and its dependencies! Look
We basically have a partial |
75f24f1
to
91d00d2
Compare
I'm going to merge this in a few days, unless somebody is contrary. |
fixed and rebased on current master |
94ed02f
to
78fa44e
Compare
@rvl ok the example was misleading because EDIT: you better use |
07dd929
to
ad57007
Compare
@anderspapitto optimized the diffing part, but note the most expensive operations are due to unpacking and repacking the whole image. I tested and on 15 seconds total, with this simple optimization you can gain less than 1 second only. |
Thanks @lethalman, it works well. Is there anything we can do to help getting this merged? I reviewed the code (or tried to). The only questions I can come up with are:
|
@rvl thanks for testing.
|
As for the last bits todo:
|
ad57007
to
b08dcfe
Compare
I should have addressed the last bits too, and the docs about the default values for I've rebased on top of current master. Further testing is welcome before I'll finally merge this :) |
Great, it still works for me. A list of derivations in |
@rvl thanks for testing. How should it be reworded? |
Instead of "contents is a derivation that will be copied in the new layer of the resulting image", try "contents is one or more derivations that will be copied in the new layer of the resulting image." I think that would suggest using a list if necessary. |
What's the status of this? |
@jgillich mergeable. I'm going to rebase, retest and merge right now. |
Awesome! 👯 |
b08dcfe
to
4a4561c
Compare
Tested |
Nvm it's now somewhat consistent. I guess there's still some non-determinism somewhere in |
Composable docker images with Nix
Merged. No reason to keep this unmerged, it can be improved over time and already does a very good job I think. |
👍 |
Wow, this is pretty awesome looking! |
@lethalman Would you know, is something special required for hydra on nixos to be able to build image with runAsRoot (requiring kvm)? Images without buildAsRoot build fine. Images with runAsRoot build fine as a user, but not by hydra (on the same machine). |
@lethalman Sorry for bothering. It seems that |
So I wanted a Nix function to create docker images. That's easy, just make a tarball of the closure of a derivation, and it's ready to be imported.
The problem is: how do you add more content to an existing image, by using layers?
So here's an attempt to provide a solution, even if it's very ugly.
Let's start with a simple bash container:
Now let's create another image with python only:
Now it comes the magic. Let's layer that python image on top of bash:
Note: the baseImage is there only for convenience. One might as well generate a
Dockerfile
on the fly, and use the sameimage.tar.gz
. I haven't done that, but it's easy to do.In other words, we're able to take arbitrary nix containers and layer multiple of them with minimal overhead.
Now that 184mb is because for some reason gcc is pulled in with a lot of other stuff, most of which are useless for doing the layer merge. The python closure is only 36mb, so... there's a lot of opportunity to optimize.
Just think a debian docker is around 150mb, so we aren't that huge either.
cc @offlinehacker @datakurre @domenkozar for discussion :)