Container Images: Interactive Deep Dive

Yves Brissaud, Senior Software Engineer, Docker

Recorded on November 15th, 2023
This talk dives into OCI (Open Container Initiative) images. We start by building an image, then explore the internal of the image and its relationship to registries. In the end, you will have a better understanding of what an image really is, including the limitations and the possibilities to extend it.


Welcome to this talk. I hope you enjoyed the keynote this morning. Well, this is something a bit different, but that’s really a core part of all the work that is all the images. And I will show you my vision of images and how we can better understand how that’s working. So first, I’m Yves. I’m a senior software engineer at Docker. I’m working on the CRI and the backend stuff on Docker.

Before we really dig into the topic, I just want to explain a bit why it can be interesting to care about the images. For four years I’ve worked on Docker; I’ve worked on several different parts of Docker. I work on the registry to find inconsistencies inside the storage of images. So it was all about images. I worked on publishers, like DVPs, and the software and source program. We created some analytics, like the pool analytics and all of that, images, the tags, the pool, etc. And now I’m working on Docker Scout, but it’s the same thing. I still work directly in images. I work on the tags and how we can extend images, add more content inside images.

At the beginning, images were just something kind of magical. I mean, like when you just start Docker, you run something, you don’t know what’s inside an image. But the more I learn, the more I work on this different topic, every time we go with the same question, what is an image? Exactly.

What’s the relation between a tag and an image? And it also looks magical. Sometimes you don’t understand exactly what’s working. And in fact, it’s kind of simple. I mean, it’s easy to just dig into these images and better understand all that stuff. And the more you understand that, most of the time, the more you can do your work properly.

Just what this talk is about? We will discuss images, I mean, a push and pull. What happens when we push a new version of an image, how we can extend, how we can go behind the images the way we think most of the time. But what it will not be about is the specification. If you want to see something about the specification, everything is on GitHub. It’s pretty well explained. I will just not list the full spec. It doesn’t make sense here. And one last thing, there’s the slides available. So if you prefer to watch the slide, or if you want to see them later, everything is already available online. It is the exact same as I will present today.

Table of Contents

    Build a simple image

    If I want to learn about images, the first thing I need is an image. So let’s build a simple image. Most people already know how to do that, so I will not explain the build itself. We build an image. I mean, it’s based on another one, base image for multiple architectures. That’s something, for several years, lots of people don’t really look at platform images. But now, especially with, like, since people have mac M1, they start to have another architecture on their own laptop. So we start to need multi-platform images all the time. We will do that.

    We’ll just create some kind of basic image. I will add some supply chain materials. So I will not dig into this. I mean, there’s other tools if you really want to understand what it means, but just to have a real example. And we’ll produce that under different tags. So let’s do that. I have just a very simple Docker file. I mean, it just doesn’t really matter what’s inside. It’s just from the long image, I’ll just add some packages and do some JavaScript code. There’s nothing really special. And so it’s my build. So something you probably already know, I mean, just the build, my two platforms, as I said, so Linux AMD64, Linux Arm64, the two attests.

    I will not begin to really dig into details of that, but it’s just the way we can just add the SBOM. So it’s all the details of the image, all the packages, the licenses, etc. And the provenance, it’s everything we record at the time of the build — what was the digest of this image, etc. And we have four tags. I chose this title. It’s like, it’s not necessarily what you do most of the time, but when you’re using the base images, they are done that way. So like if you take the Alpine image, you can pull the Alpine latest, Alpine 3, etc. That’s just the way you can select your channel and pick the one you want. So just replicate that. We can do whatever we want to start, but just as an example. And I will push that. Just one quick thing. I mean, we have some locales, etc. I have my own registry on my laptop, so just to avoid some kind of network history that’s not as much as possible. So let’s build that, which should be pretty fast. Yeah, pushing. Okay. So I have my image. And I mean, I can just run it. And yeah, hello.

    What’s inside?

    So that’s the beginning of the story. I have my image. And now what I want to do is to understand what’s inside. What we will do is to open this image, not run it, but we open the internal of the image and start to navigate inside the code. To do that, I mean, it’s what’s important. We just create a local directory. And there’s the Docker save command that exists. So the Docker save will take an image, put that in a tar in an archive, and we’ll just archive it and see what’s inside. So, extract.

    All right. So I’m just creating the local directory, my Docker save, and just archive it. So that’s all the content of my image. So a lot of blobs and just a few of the files and inspect and just see, I will just open, I mean, VS code to see that. Okay. This is the content of an image when you want to read. So most of the time, you don’t see that because there’s a lot of different files. So we just store them differently. We’ll see a bit later how it’s stored inside the registry. But that’s one image. If you do a save, that’s what you have in your archive.

    So we’ll start just very briefly with the OCI layout. So OCI means Open Container Initiative. It’s now the specification of the images. Before, there were also the Docker images, but everybody is using OCI now. It’s the best specification we have. And we just say, okay, this is the version one, one zero. Okay, it’s fine. The manifest JSON is the Docker image way. The index JSON is the OCI way. So we’ll just go to the index JSON. So you see there’s a lot of JSON everywhere. This part of it is just the entry point. There’s nothing really special. I mean, it just says this is the image I just extracted. And the thing we have all time is media type and digest and also size. So every time we have some content somewhere, we just create the digest of this content. And this is the way we reference any content.

    The cool thing with that is if we have two times the same content, we have the same digest. And so we don’t duplicate the content. And we have media type. So in that case, we just say, okay, this is an image index. This is the index. This is where I should start.

    Image digest

    So, that’s what we will do all along the way. We’ll just pick this digest, look inside, and so on. So pick this one, the three eight. Oh, yes. Just go to that. Okay. So it’s again a digital file. We just can check that this is an index. And it’s just basically an index of manifest. It’s just a list of manifests. That’s all we have inside. And for all the manifests, what we have is the platform. So we only started here with the multi-platform aspect of the image. We’re just doing, so this one is the Linux AMD64, below with the Arm64. And for each of them, we just say, okay, this will be the image manifest. And this is the digest of this manifest. For the Arm64, I mean, it’s exactly something, just a different digest, just different content.

    We also have two others manifest that are a bit different because the platform is unknown. So unknown means, I mean, except if you have some computer that has a non-platform, it’s when you can’t run it. So it’s something a bit different. It’s still an image manifest. But we will dig a bit deeper inside the content of that later. But we have other manifests in our image. There are some annotations. That’s what we can see here. We have a type. So it’s an attestation manifest. During my build, I just add this, a test. So we can think that this is the same thing.

    And what’s interesting in this annotation? I mean, the annotation, you can just tell everything you want. There’s no real rules on that. But we have one that is a reference. So there’s the digest of this manifest. And we’re storing a second digest. But this one, the C1, it’s this one. So we start to create links between the different manifest inside my image. And what we will do with that is we’ll start to build some kind of visual representation of the image.

    Image index

    So the first thing we display is the image index. And then we have four manifests. And we have this relation between two of them each time. So one manifest for the AMD64, one manifest for the Arm64. And the two attestation manifests, one pointing to this one, and one pointing to this one. That’s the base of our image. But let’s go a bit deeper. So I will just pick the AMD64 by default. I mean, but I could have picked any other.

    So let’s go down into this digest. Okay. So it’s the same thing. I mean, it’s again, a JSON file. It’s just different. I mean, this time we have two main paths. One is config, one is layer. And I mean, we just have the media type that is manifest as expected. The config is everything you put on top of the content of your image when you want to run it. So if we go to this file, DB7. So again, JSON file. And we’ll find everything we need to run the image — like this is the environment variable. This is my command. So it’s exactly what we can find inside the Docker file. Or I mean, with the environment, the buildups or whatever we display like the I mean, we said the working directory. This is all we run the image exactly. And we also have some config history part.


    So it’s all the different layers, all the different instructions we have inside the Docker file that are stored in this blob. And last, we have this diff ID — this is the uncompressed digest of the content. It’s exactly the content that will be part of the final image at the end. But, yeah, so this is the config.

    And we have some layers. I guess most people have already heard about layers, because that’s a pretty common term. A layer is just a set of blobs of content. We’ll not go deeper inside. Here in the layers, I mean, it’s jzip; it’s just archives. It’s all the files of the images. So we have different layers, and we combine them. Some layers can add files, some layers can modify files, some can remove files. And we just stack all the layers, and we have the final filesystem of your image. Then with the filesystem and the config, we can run the image.

    If we just go back to here, that’s the image. We have the index, we pick the manifest for the AMD64 platform, and then we have one config block and several layers. It’s what we have all the time. If you look at the Arm64, we will have exactly the same thing. Most of the time, that’s what people call image. This is really the asset that you can run at the end. The goal is, we get this asset or we store this asset but that’s what you want to run at the end. And if we combine all that, so like one or multiple images and the index, this is what we call multi-platform image. This is just one step on top of that.

    But let’s have a look at what these two, the two attestation manifests. If we go back here, this is my image. So I’m just going back to the index. I will just pick like this one, for instance. It’s an image manifest, so it should be the same thing. So EF, where is my EF? Yeah, here. Again, it is an image manifest. We have a config, and we have some layers. If we go to the config, it will be slightly different. So this one, just because there’s almost nothing inside. This is not reliable. I don’t care about environment variables. I don’t care about commands. I don’t care about certain three points. This is not a reliable image. But this is still an image. And if I can just close this, and if we look at the layers, they are slightly different. The first thing is it’s a completely different media type. But it’s still a layer. But it’s in toto. So it’s absolutely the content with all the files. We have the digest, I mean, as expected. And we added again some annotations.


    An annotation is just a way to give metadata on top of that saying, because we have two layers here. Both of them are in toto, but they are not the same content. So one of them is the SPDX document. So this is the SBOM, the software bill of material. This is all the packages of my image. The other one is the SLSA provenance. So all the context at build time. I can show very briefly what we have inside, but that’s not really the purpose of this tool. So it’s all but just, it means we can store everything we want in there. So it’s not yaml; it’s JSON. All right, so this stuff is just a file. I will not go through the file; it’s just everything you have. You can store that, you can store everything you want. And the packages, all the licenses where you can find them inside your image. This is some kind of metadata, and as a layer of an image. And it’s the same thing for the provenance. So if we just try to complete this diagram for the attached session manifests, we have the same thing. This is not the same mediatype, but at the end, it’s just one config blob, even if it’s pretty useless, and two layers.

    With that, this is what we can see — the only thing I didn’t show in the internet is just the blob itself, I mean, the layers of the image to run, but it’s just files. So we don’t care about that. But the thing that is interesting is, I mean, I have this image. I can run it. But what I want is to push most of the time. That’s really the important part of that. And just why to push the registry? I can just archive that and put that on any server. But the cool thing with that is deduplication. That’s probably the most important part. I mean, every content that is identical will produce an identical digest. So I will store it only once. At the level of registry, this is huge, because there’s a lot of people creating the exact same content a lot of time. Or just to reuse someone else’s content. So we just deduplicate everything. What we also want is metadata. I mean, this image is just about digest. So if you don’t want to just remember the exact digest of the image you want to run all time, we want this extra layer of metadata on top of that.

    The last thing is versions. We want to keep all versions of everything. Like if you push your image on the latest, maybe tomorrow you push a new version of the latest, but you don’t want to break people using the previous one. So you need to keep track of all the previous versions all the time. Obviously, there’s a lot of them on the registry. I mean, it can be authentication, permission, etc. But this is really the best of that.

    Push an image

    Now we’ll see how we push an image directly to the registry. So it will be mostly to push all the blobs, all the content of the blob folder. I want to push that on my registry. So it will be the layers, the config, but also all the manifests. Basically, everything under my blob, I want to push that. And then I want to create the tags, all the metadata on top of that.

    So that way it will be stored in the registry, it’s pretty similar to what we have. So if I just go back to the content here, I mean, what we have is these blobs, SHA 256. It’s on the registry, it’s roughly the same thing. We just have this extra level, intermediate level. It’s just the two first characters. I mean, we just segment the blobs, but at the end, it’s exactly the same thing as you have locally. So this part, I mean, it’s pretty, you just push all the blobs. You don’t really care about what’s inside, you just push all of them.

    Then, when it starts to be very interesting is when you want to push the tags. So this is a bit more complex. I mean, there’s just more layers. I mean, like the repo name, and we have the tags. So I just represented, I mean, there’s the one zeroed, and the one zero zeroed, so I just push. And then, there’s more stuff here. So if I just focus on just the letters to better understand that, the first thing interesting is the link is the final file. What I want is, I want the current version of my title latest. This is this file. But sometimes, I want the previous version of my latest index. So it will be this one. I mean, here we will just store all the different digests recorded to this tag. It will just make more sense later when we’ll push different types.

    And if we put that together, this is the registry. This is exactly what’s inside distribution. But this is also exactly what we have under the current right now. This is stored exactly that way. So we have all the blurbs, the repositories, and all the tags in between. And what’s inside this link files here? It’s just the digest. So all these files links just contain the digest of my image. So maybe my image index. That’s where you start to connect all the dots.

    Let’s say we push an image. So we push the blobs. And then we store under the manifest tag, so the different tags, the current version of each of them. And we just store the version, I mean, the index one, the historical version of that.

    Pull the image

    And now what I want is to know how we can pull that. And in fact, it’s not that difficult. I mean, there’s mostly three different steps. The first one is I want to translate my tag to a digest. Like if I pull Alpine latest, I mean, I want to find this. I mean, everything is stored as a blob, referenced by its digest. So I need to know what is my digest.

    The second thing, because we have multi-platform images, is I will get the index, and I want to know which is the right platform I want to pull. Because maybe I just want to run for Linux, AMD64. So I don’t care about the others, I just want this one. So I will try to pick this one first. And then I will really download the content of the image. The first part is just to give me access to these config and layers.

    Once I have the config blurb and the layers, I can combine them and run my image. So it’s just a few HTTP requests. It’s pretty simple. I just do a head request on my manifest latest. So I mean, manifest latest means the tag latest. And the answer will be, okay, this is an image index. And this is the digest of your content.

    So what I get is this file. I just get the content of this file. So I’m asking for the latest tag. And I said nothing else. So I want the current version of this tag. So give me the content of this link. And this link just contains the digest of my index. So with that, I’m creating the tag the user is asking for two digests. So I can get the digest. I can get the manifest by this digest this time. And I will do a get. I mean, the head request, we just written nothing at the headers. Now I will just get the manifest. But particularly, this image index, we already show the different manifests inside. And all of them with the different platforms. So what I get here is this image index.

    So I start to really go down inside my image right now. And what I want in this list of manifests is to pick the right one for my platform. So I’m on Linux AMD64. I will just pick this digest here. And now I will just continue that way until I go to the final digest. So this is the image manifest. That’s what I want. I just get the same way by digest. And I will get this image manifest. So the image manifest is this part of the image. And if I read it, I have the config with the digest. And I have all the layers with that digest. So with that, I will just continue to do some get requests. This time, it’s not at the same place. I mean, it’s inside the blobs part. This is just how I can get to this information. This is the real one I want to get to run an image. And this is what you saw when you “docker pull” an image. In that case, docker pull with the platform AMD64. And I will see all this line with some part of the digest. It’s just a short digest. And in that case, I mean, it’s the answer. It’s complete, so it will get the image index, get the image manifest, get the config blog, get the layers, etc.

    That’s everything we just did. I mean, we can do that by hand. I mean, it’s just a few requests. I mean, not that difficult. A few requests. Just read some JSON files. I mean, that’s what Docker is doing when you docker pull. Exactly. Then, I mean, you can also do some parallel stuff, etc. You can make it complex. But if you want to write something that will pull an image, it’s just that simple. And with that, we just get all the rest of the image.

    So this is just the kind of summary of the pull. But this is all the requests we have to make to just pull an image. Nothing that difficult, that is, the images are pretty simple. As everything is just addressable, you just have to know the digest. And to know the digest you want to pull, you just have to read some JSON files, and that’s it.

    Now, what is interesting is I pull the image latest. But what if I want to pull the image one? The thing is we build one image, and we target four times. I pull the latest, and now I want to pull the version one. So we just go to these three exact same steps. So first one, we convert the type to digest. So it will be one and not latest this time. We find the right platform, and we don’t know the content. But the cool thing is, there’s only one request to do here. I mean, because when we convert the type to the digest, this is the exact same digest and then latest. And it has been stored on my machine.

    So I have this digest. I will just read, again, the same file. So this interaction is really important. I mean, because this is really the same file. And we’ll read the same file. I will pick the right platform. I already have the digest of the, I mean, the blobs, the manifests of this platform. I read the digest of the config blob of all my layers, but I already have everything locally.

    At the end, it’s only one single head request, and I have my new image. So obviously it’s kind of an extreme case, because most of the time you don’t pull two times the exact same image. But if you have an image that just adds some content, and we’ll start with the data, I mean, we already see that some requests will not make them, because we already have some part of the content locally. So what we did, I mean, whatever it’s latest, it’s one. We start with this link. I mean, once you have the latest, you just get this link. This is the same content, it’s done.

    New image

    Now we can do something very interesting. That’s good. We have a image, we are able to push it, we are able to pull it. But what I want is to do a new image, because I don’t know, there’s something to fix, whatever the reason. So let’s do that. Let’s do some just very quick changes. So I will be quite simple on that. Hello, DockerCon LA. I will just rebuild that. So this is the same build command. Except one line. I will have the same tags, except this one. So this is my new image. Maybe it’s a very important fix. I want this bug fix, so it will be called 101. But I still want people that choose to use maybe the tag 1 to still have this image. So I will retag letters 1 and 101. But I will create a new tag 101. But I will not change the 1.00. So let’s see what, I mean, just, yeah, first I need to build it, it will be better. Okay. I mean, I can run it. Yeah, I mean, it’s my new one. I mean, I didn’t put the tag, but it means latest.

    This is the new version of my latest image. Okay. I can just extract this new version. So the 101. Basically, it’s exactly the same thing. I mean, the same structure at least. And what we can see. I just prepared a comparison. If I’m just able to type the right one. Yes. So I just mixed the two. I mean, the first image I extracted, and the second just to see the differences.

    And what we can see is we have new blobs. It’s a new image. There’s new content. There’s a new tag. But some of them, I mean, all the ones in white are exactly the same. They existed in the first version of the image. And they still exist in the second version. So locally, I mean, if I do not say I just duplicate everything. But there’s some content that is the same. I only change one part of the Docker file. The base image is the same. The package is added. The same. A lot is read the same. But some are not, so we’ll not go through all the details. But this is what changed and not.

    Some layers there didn’t change — the base image, the packages, etc. But all the rest changed. Like my manifest is not the same. The final image. I mean, like, let’s say this one here is where I just changed DockerCon to DockerCon LA. So it’s a new one — new content, new digest, as I read from them. I changed the digest of this layer. So the manifest changed. I mean, the content changed. So the digest of the content changed, etc.

    So there’s some stuff that is the same. Some stuff that is not. And yet, the attestations are different because we build at a different time. There’s some time stamp in there because that’s what we want to know. So the content changed. But yeah, we just try to limit as much as possible the changes everywhere.

    And if I push this image to the registry, that’s what I’m doing. So I’m pushing this 101. So this is what we saw just before. It’s the current version, the index one. So we did digest. I mean, it’s the historical version. But I didn’t change the 101. Because I didn’t retag it. But I retagged the latest one. So I did two changes. The first one is this link. This is the current version changed. When no, I want to pull this image. I want the new version. I don’t want the previous one. So I also added this new version in the historical versions. But I still keep the previous one. So if I try to pull the latest — I mean, by saying I want this digest of latest, it still works. It’s still there. But by default, it will be the new one. So if you look at the ration between these tags and the different blocks. So I have, for instance, on the 100, I mean, this link, that’s go to one digest. And then, I’m going to do the first one. And this new link just points to another digest. And in latest, I just have both.

    This is where we will store all the versions. And so, yeah, this is quite common. This is sometimes a very good practice. Like, you want to pin an image. So we’ll say, okay, this is this target but with this exact digest. So if someone changed the digest, the target, pushed a new version, my build will remain the same, especially with latest. So people sometimes really push a lot on latest. I still want, sometimes, I say, like, I validated my image with this digest and not another one, so I want this stuff exactly. That’s why we are storing everything. So this is the way we create an image, push and pull an image.

    Beyond images

    But there’s something that is a bit interesting. Because we can go way beyond that. It’s like, I have images to run them. I just embed some stuff. But most of the time, it’s just my different layers with files and the config, and that’s what I want. But, in reality, we can go way beyond that. And we can see that in two different ways. One of them is we’re not forced to store container images. We can store whatever we want. I mean, it’s just as soon as you respect the specification, as soon as you respect the manifest image, manifest, and image index, the rest is exactly what you want.

    The second thing that I found really interesting is how we can extend the container images. Because we can store inside the image, very closely to the data of the image, we can also store everything we want. So just to give you some example of that.

    First, we can store everything. There’s one new command, for instance, in Docker compose, when you can publish your Docker compose. So what you will do is it will be an OCI image containing your Docker compose YAML file. And it’s just an image.

    If you look at the manifest, this is my image manifest. There’s the new artifact type, just say, okay, this is Docker compose project, okay, that’s fine. There’s a config, a config blob, as expected. Just the size is two. So let’s say there’s just nothing inside. And, basically, we’re just storing the annotation here. I mean, the annotation that is just the compose version used. And we have some layers as all the others image. But the media type this time is a compose file YAML. It’s not a JSON, it’s free, you compose file. And this is the digest. So if you go to find this digest, this blob, and you read that, this is your YAML file. But stored as an image.

    So once you store the image, it’s when you can share it across all the different registries. You can use the same tools. If you have a tool that just follows the different links, the different requests, translate your tag to digest, go down to the manifest, etc. It’s working the same way. So if you want to pull a compose file or an image, this is exactly the same thing. So that’s a new command. So I think it’s dash dash publish on Docker compose now. So you can test that. This is one thing. If you have a Mac, you probably already using HomeBrew. But HomeBrew can also be stored as an image manifest.

    Again, this is not an image. You will have different layers. But you have it. And the cool thing with that is, again, you will store that on the registry. You will have the deduplication, because the deduplication is really inside the protocol, inside the way we’re storing images. So it’s just out of the box. And registry are just everywhere right now. So you can have HomeBrew as an OCI image manifest also. We can have a CNAB — I will not go into details of that. CNAB is a way to combine multiple images in one big package, to have one application that maybe contains multiple images. But the thing is, we can have image index referencing image index. And so we can store image in image in image if you want. So we can combine a lot of images inside one single asset. And as long as you just follow the different links, it just works. So just another way. We don’t need to create another tool to do that, we just use the exact same registry.

    And there’s more. I mean, there’s already more. And we’re using that for Helm charts. You can store your Helm charts in the registry, along with all your images at the same place. We can store WASM modules that are a bit different than the container images, or Docker volumes, dev containers, etc. So there’s a lot of stuff we can store inside the same registry. So the registry is kind of agnostic of that. And the specification, maybe there’s some stuff to change, but we can store anything you want there.

    But the really cool thing, at least from a point of view, is to extend images. So if we go back to the first part, the first image, we have all these four layers. So that’s the two attestations, so where we store the bill materials roughly. The really good thing is it’s stored inside the full image. So it’s not something like we store somewhere else. I want the SBOM, I want the description of my image inside my image. I don’t want that somewhere else. So you can do that. It’s really cool because you’re using, again, the exact same tool. When you reference an image, I can choose to pick the part that I will run. But I can also pick the part I will just analyze, like to display vulnerabilities, for instance. But the cool thing with that, I mean, at least my question I have, and probably for you, is what should we store? I already did some highlights like how we can embed documentation inside the image. So you just run the image and you have the full documentation of the image, or you can run it. Maybe you can use that to store runbooks or whatever.

    The thing is, you can just store everything you want, very, very closely to the runnable image. And then, it’s just a few requests and you can create your own tool. So maybe, I don’t know, you want to create a tool for, to display documentation, maybe you want to create a tool to, I mean, whatever.

    I’m really curious to see what people can build with that because it’s very simple. I mean, images are most of the time something quite magical, it’s just kind of black box. But it’s just a bunch of different files. So it’s very, I mean, you can even create an image by hand. I mean, it’s just create your content, create the digest of that, and create a JSON file and boom, you have your image. It’s just that simple.

    OCI image specification

    And just one last thing. So at the first, I mean, at the first look of the image, we have this OCI layout. And we saw some annotations. Annotation, the problem is it’s too generic. It’s just you can do just what you want. That generosity is good, because you can do what you want. But the downside is you can do what you want. So it’s harder to create tools on top of that. So there’s a new version of the specification — it’s the new change that will come to the specification. So it will be the version 1.1.

    So there’s three things. The first one is to help create non-container images. The second one is to create some very specific fields to create this relation, let’s say, between the attestation and the right image inside. So, once we have these fields, we can create the right API. So we will have some API, just say, okay, I have this reference. And what I want to know is the relations between the different parts of my image.

    So these are the new changes for the specification that will help to create new content, extend content. And it’s just an exciting moment. I mean, I’m very happy to see these changes. So we’ll see what people will build with that.

    And that’s the end. Thank you for being here. And I hope we will build a lot of new tools with all that stuff. Thank you.

    Learn more

    New to Docker? Get started.

    This article contains the YouTube transcript of a presentation from DockerCon 2023. “Container Images: Interactive Deep Dive” was presented by Yves Brissaud, Sr. Software Engineer, Docker.

    Find a subscription that’s right for you

    Contact an expert today to find the perfect balance of collaboration, security, and support with a Docker subscription.