Week 11

Deployment Strategies

Creating Docker Images

Multi-stage builds

Docker Compose

Container Registries

Deploying to the Cloud

Database Migrations

Practice

Assignment

Backend Track

From running to building

In the core program you ran containers that other people built: you pulled an image from Docker Hub and started it with docker run. In this chapter you flip to the other side and build your own images and learn how to pack your real Spring Boot application in its own Docker image.

<aside> πŸ’­

This chapter goes deeper into Docker. If you need to refresh your knowledge, revisit the Docker topics from the core program:

Watch: Docker Core Concepts Every Developer Should Know

<aside> πŸ’­

The following video is the same Docker video from the core program. Back then you stopped at the Layers section. Re-watch it now from the beginning until the end.

</aside>

Docker Core Concepts Every Developer Should Know

Key takeaways from the video

What is a Dockerfile?

A Dockerfile is a plain text file (no extension) in the root of your project. It contains a list of instructions that Docker executes from top to bottom to produce an image.

Each instruction creates a layer: a cached snapshot of the filesystem at that step. If nothing changed in a step, Docker reuses the cached layer instead of executing it again. This is why the order of instructions matters β€” you will see this in action below.

Your first image

Let's build the smallest useful image we can: one file of plain Java, no Spring Boot.

Hello.java

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello from inside a container!");
    }
}

Dockerfile

FROM eclipse-temurin:25-jdk
WORKDIR /app
COPY Hello.java .
RUN javac Hello.java
CMD ["java", "Hello"]

<aside> ⚠️

Note: The file names Hello.java and Dockerfile are capitalised.

</aside>

What each instruction does:

<aside> ❗

RUN happens once, at build time. CMD happens every time you start a container. Mixing these two up is the most common Dockerfile mistake.

</aside>

Now build and run it:

docker build -t hello-java .
docker run hello-java

<aside> ⌨️

Hands on: Create a new empty folder with the two files above, then build and run the image.

</aside>

πŸ’¬ Run the docker build command a second time, without changing anything. Why does it finish instantly?

πŸ’¬ What about ENTRYPOINT?

<aside> πŸŽ‰

Well done! You have just created your first Docker image

</aside>

Managing Docker images

Images live on your machine until you remove them. A few docker image commands cover day-to-day management:

Command Description
docker image ls List all images on your machine
docker image rm <name> Remove an image from your machine
docker image prune Remove dangling (untagged) images to free space
docker image inspect <name> Show detailed metadata about an image

Use docker image ls command to find your image:

> docker image ls
                                                                                                                       
IMAGE                               ID             DISK USAGE   CONTENT SIZE   EXTRA
hello-java:latest                   e9a5a6457ee9        438MB             0B    U
mongo:latest                        b8b32a56a1dd        934MB             0B    U
postgres:18                         da601a920445        479MB             0B

To remove the image, enter the command

docker image rm hello-java

<aside> πŸ’‘

You can also manage your images in the Docker Desktop app

</aside>

Containerising a Spring Boot application

Your Spring Boot projects already build into a single executable JAR file:

./mvnw clean package -DskipTests

This produces target/<your-app>.jar. Running a JAR does not require the full JDK β€” a JRE (Java Runtime Environment) is enough, and the image is much smaller. So the Dockerfile is short:

FROM eclipse-temurin:25-jre
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Build it from your project root:

docker build -t my-api .

<aside> ⚠️

</aside>

Running your container

Port mapping

A container is isolated β€” your Spring Boot app listens on port 8080 inside the container, but nothing reaches it from outside until you map a port:

docker run -p 8080:8080 my-api

The format is -p <host port>:<container port>. The left side is a port on your machine; the right side is the port inside the container. They do not have to match:

docker run -p 9090:8080 my-api

Now the app answers on http://localhost:9090, while inside the container it still listens on 8080.

Environment variables

Containers do not see the environment variables of your shell β€” isolation again. You inject them explicitly with -e:

docker run -e "API_KEY=xxxx" -e "JWT_SECRET=xxxxx" -p 8080:8080 my-api

Inside the container, the app reads it like any environment variable. Later this week, environment variables become the way you configure your deployed application.

<aside> πŸ’­

</aside>

Running in background

To run your container in the background, add the -d flag (detached mode).

docker run -p 8080:8080 -d my-api

Volumes

Everything a container writes to its own filesystem is deleted together with the container. To keep files, mount a folder from your machine into the container with -v: