Container images
Now that you know what a container is, know what's inside a container image, how it's built, where it lives, and how Google builds one for you. An image is an application plus its dependencies; a container is a running instance of an image.
Images vs containers
Building software into container images lets developers package and ship an application without worrying about the system it runs on. To build and run those images you need software - one common option is Docker.
| Docker can | |
|---|---|
| Open-source technology | ✅ |
| Create and run applications in containers | ✅ |
| Orchestrate those applications at scale | ❌ - that's what Kubernetes does |
Docker creates and runs containers but has no way to orchestrate them at scale - that is Kubernetes' job. Later you'll use Google's Cloud Build (below) to create Docker-formatted images without running Docker yourself.
What isolates a container
A container's ability to isolate a workload comes from a combination of four Linux technologies.
- Linux process - each has its own virtual memory address space, separate from all others, and can be created and destroyed rapidly. The foundation containers build on.
- Linux namespaces - control what an application can see: process ID numbers, directory trees, IP addresses, etc.
- Linux cgroups - control what an application can use: maximum CPU time, memory, IO bandwidth, and other resources.
- Union file systems - bundle everything the app needs into a set of clean, minimal layers.
The Linux namespaces that isolate a container are not the same thing as Kubernetes namespaces. Same word, unrelated mechanism - a classic mix-up.
Image structure: layers
An image is structured in layers. The build tool reads instructions from a container manifest - for Docker-formatted images that's a Dockerfile. Each instruction specifies one layer. Every layer is read-only; when a container runs from the image it also gets a writable, ephemeral topmost layer.
Each command creates one layer (this example is oversimplified for teaching):
FROM ubuntu:18.04- creates a base layer pulled from a public repository (here, a specific Ubuntu runtime).COPY ./app- adds a layer with files copied from the build tool's current directory.RUN make/app- builds the application and puts the result into a third layer.CMD python/app/app.y- specifies the command to run inside the container when it launches.
Order layers least likely to change at the top, most likely to change at the bottom - so cached upper layers stay reusable across builds.
The ephemeral container layer
When a container launches, the runtime adds a new writable layer on top - the container layer. All changes to the running container (writing, modifying, or deleting files) go to this thin writable layer, and they're ephemeral: when the container is deleted, the layer's contents are lost forever. The underlying image is unchanged.
Because the writable container layer is ephemeral, permanent data must be stored somewhere other than the running container (a database, object storage, a mounted volume). Anything written only to the container layer disappears when the container dies.
Shared layers = smaller, faster images
Because each container has its own writable layer, multiple containers can share the same underlying read-only image while keeping their own data state. This lets images shrink with each layer: a base image might be 200 MB, but the next point release differs by only ~200 KB - so the build creates a layer with just the difference instead of copying the whole image.
When running a container, the runtime pulls only the layers it needs; when updating one, only the difference is copied. That's far faster than spinning up a new virtual machine.
It's not best practice to build your application in the same container you ship and run - build tools are clutter at best and an extra attack surface at worst. Modern packaging uses a multi-stage build: one container builds the final executable, and a separate, minimal container receives only what's needed to run it.
Getting and storing images
It's common to start from a publicly available open-source image, as a base for your own or for unmodified use.
| Source | What it is |
|---|---|
Artifact Registry (pkg.dev) | Google-maintained public open-source images, plus a place to store your own private images. Integrated with IAM. |
| Docker Hub | Public third-party registry of container images. |
| GitLab | Git platform with its own public container registry. |
Cloud Build
Cloud Build is Google's managed service for building containers, integrated with Cloud IAM. It retrieves source code from Cloud Source Repositories or git-compatible repos like GitHub and Bitbucket.
You define a series of build steps - fetch dependencies, compile source, run integration tests, or invoke tools like Docker, Gradle, or Maven. Each build step runs in its own Docker container. From there Cloud Build delivers the newly built images to execution environments including GKE, App Engine, and Cloud Run functions.
Two ways to drive a build
--tagwith a Dockerfile - point Cloud Build at a directory containing aDockerfile; it builds the image and pushes it to Artifact Registry in one command.- A
cloudbuild.yamlbuild config file - list explicitsteps, each a container. This unlocks more than building: run tests, push to multiple destinations, deploy.
A build config's steps can run the image they just built and fail the build if a
test fails. gcloud builds submit then returns a non-zero exit code, so a CI
script can act on the failure.
In a build config, $PROJECT_ID is a substitution variable Cloud Build fills in
with the project ID of the build - you don't set it yourself. Building via a Dockerfile
and --tag requires no config file at all.