Java 301 · Deployment

Containers, Docker & Docker Compose

How to package an application with everything it needs so it runs the same on every machine — and the Docker commands that build, run, connect, and orchestrate those packages.

Updated August 2026 · Read ~18 min · Session 9


// the big idea

A container bundles your code with its runtime, libraries, and config into one sealed box, so it behaves identically on your laptop, a teammate's machine, and production. Docker is the tool that builds those boxes from a recipe (a Dockerfile), runs them, and — with Docker Compose — starts several at once and wires them together.

Section 1

Notebook Summary

Dense, hand-copyable revision sheet. Every line here is expanded in the Detailed Notes below.

Why containers

Container vs VM

Container VM
Size small (app + deps) big (full OS)
Start/stop fast slow
Kernel shares host's own kernel
Isolation weaker stronger

Docker core

Dockerfile → image → container

Ports & networking

Docker Compose

Command cheat sheet

docker pull <img> fetch image from registry
docker build . -t n:t build image from Dockerfile
docker run -p h:c <img> start a container
docker ps list running containers
docker stop <id> stop a container
docker rm <id> remove a container
docker image rm <img> remove an image (containers first!)

Section 2

Detailed Study Notes

You finish a feature on Friday, it works perfectly on your laptop, and you push it. Monday morning a teammate pulls the branch and nothing runs — a different Java version here, a missing library there, an environment variable no one wrote down. The code never changed. The surroundings did. Everything below exists to kill that failure mode: package the surroundings with the code, once, and ship them together.

2.1The deployment problem

Software is built in phases, and the last one — getting it onto users' machines — is where "works on my machine" bites.

A software project moves through a life cycle of about seven phases. For this lesson only the last three matter: the development phase (splitting the project into modules and turning requirements into code), the testing phase (validation and quality assurance — catching functional, UX, and security issues), and the deployment phase (delivering the finished product to its intended users and making it actually usable). Deployment is the step where the environment mismatch above shows up.

Analogy · the cargo ship

Picture loading a ship with cargo of every shape and size — furniture, fruit, machines. Loading each item loose is chaos. Instead you put everything into standardized shipping containers: now any item can be packed, and once packed the container can be stacked, craned, and transported the same way regardless of what's inside. Software containers do the same thing — a uniform box around wildly different applications.

2.2What is a container

A container is a lightweight, self-contained box that holds everything a piece of software needs to run — the code, a runtime, libraries, environment variables, and config files — all sealed together and isolated from other containers.

Analogy · the packed suitcase

Packing for a trip, you put clothes, books, and snacks into one suitcase and you're ready to go; at your destination you unpack exactly what you brought. A container is a suitcase for your application: it carries everything the app needs, so wherever it lands it has its own supplies and doesn't depend on what's already there.

Because each container bundles its own dependencies and is isolated from the others, the same container behaves the same way on any machine that can run containers. That reproducibility is the whole point.

The natural next question is: isn't that just a virtual machine? Not quite. A virtual machine (VM) carries a full copy of an operating system, so it's large and slow to spin up but strongly isolated — it has its own kernel. A container carries only the app and its dependencies and shares the host machine's kernel, so it's small, fast to create and destroy, but less fully isolated from the host.

VIRTUAL MACHINES App Bins/Libs Guest OS App Bins/Libs Guest OS Hypervisor Host OS Hardware CONTAINERS App + libs App + libs App + libs Docker Engine Host OS (shared kernel) Hardware
Fig 2.2 — VMs stack a whole OS per app; containers share the host kernel
💡 tip · why it matters

The single sentence that answers "container vs VM" in an interview: containers share the host's kernel; VMs each run their own. That one fact explains every other difference — smaller size, faster start, weaker isolation.

2.3The Open Container Initiative (OCI)

The OCI is a group of companies that agreed on shared container standards so a container built by one tool runs anywhere.

Those standards give containers two properties. Portability means everything needed to run the software lives inside the container. Interoperability means any vendor that supports the standard can run the container without changes. The OCI defines three specifications:

Runtime

How to run a container.

Image

The physical structure of a container image.

Distribution

How to distribute images.

2.4Docker: image vs container

Docker is an open platform to create, deploy, and run applications in containers. It shrinks the gap between writing code and running it in production, so you ship faster and stop worrying about setting up environments by hand.

Two Docker terms get confused constantly, and the distinction is the most important thing in this lesson. A Docker image is a reusable, shareable, read-only file — a template built from your code, its dependencies, and a Dockerfile. A Docker container is a running instance created from an image: it takes the image's read-only layers and adds a writable layer on top, and it's the thing that actually runs your application.

⭐ trick · the analogy that makes it click

Image is to container as class is to object in Java. The image is the blueprint you write once; each container is a live instance stamped out from it. You can run many containers from one image, just as you instantiate many objects from one class.

Docker Image read-only blueprint docker run Container 1 Container 2 Container 3 each: image + read-write layer
Fig 2.4 — one image, many containers (class → objects)

Where do images come from? A registry is a cloud repository of ready-made images. The default is DockerHub (docker.io); others include AWS ECR, Azure ACR, Google GCR, and Red Hat Quay. Pulling an image mirrors cloning from Git:

pull, run, inspect, clean up
docker pull hello-world        # fetch a prebuilt image from a registry
docker run hello-world         # create + start a container from it
docker ps                      # list running containers
docker rm <container-id>        # remove a container (by id or name)
docker image rm hello-world    # remove the image itself
⚠ pitfall · removal order

You cannot remove an image while a container created from it still exists. Remove the containers first (docker rm), then the image (docker image rm). Skipping this gives you a "conflict: unable to delete" error, not silence.

2.5The Dockerfile

A Dockerfile is a plain text recipe listing the commands Docker runs to build an image automatically.

Instructions are not case-sensitive, but the convention is UPPERCASE so they stand out from their arguments. Comments start with #. Four instructions carry this lesson:

Dockerfile
# Sample Dockerfile
FROM ubuntu:latest

WORKDIR /app

RUN ["apt-get", "update"]

CMD echo Hello World from Docker
Instruction When it runs Role
FROM build Parent image to build on. Must be the first instruction (only ARG may precede it).
WORKDIR build Optional. Sets the working directory inside the container.
RUN build Executes a command in a new layer and commits the result. RUN cmd or RUN ["exe","p1"].
CMD docker run The default command executed when the container starts. Same two forms as RUN.
⚠ pitfall · RUN vs CMD timing

RUN happens at build time and bakes its result into the image as a new layer. CMD happens later, at docker run time, and defines what the container does when it starts. Putting your startup command in RUN (so it fires during build and never again) is a classic mistake.

Build an image from the recipe with docker build:

# "." is the build context (current dir); -t sets the image name:tag
docker build . -t hello-world-2

# point at a custom filename with -f
docker build . -t hello-world-3 -f Dockerfile-server
Dockerfile → docker build → Image → docker run → Container

2.6Port mapping

Port mapping opens a door on the host and forwards everything arriving there into a port inside the container.

By default a container's ports are sealed inside it. Port mapping exposes a service running in the container to the host machine or the outside network: it links a host port to a container port so that a request hitting the host port is automatically forwarded into the container. The flag is -p, and the order is host first, container second.

Host machine :8000 -p 8000:8080 Container :8080
Fig 2.6 — a request to host :8000 is forwarded to container :8080
# run detached (-d), map host 8000 -> container 8080
docker run -d -p 8000:8080 hello-world-3

# now the host can reach the containerized server
curl -X POST -H "Content-Type: application/json" \
  -d '{"username": "barveabhishek-ab"}' http://localhost:8000

2.7Docker networking

Docker networking lets containers talk to each other and the outside world, while keeping each one's network isolated.

It's built on the Linux kernel's network namespace — a feature that gives a group of processes their own private network stack. Every container gets its own namespace, so its own network stack. When you start a container it's automatically attached to a default network called the bridge, which lets it reach other containers and the outside.

⚠ pitfall · same-network rule

By default, two containers can only talk to each other if they're on the same network. Two services that "should" communicate but sit on different networks will simply fail to reach one another — with no obvious error. Put both on one custom network.

bridge / mynetwork server client
Fig 2.7 — containers on one shared network can reach each other

Docker supports three network modes: bridge (the default, attaches to the default bridge network), none (disconnects the container from all networks), and host (gives the container full access to the host's network stack). The commands:

docker network create mynetwork               # make a custom network
docker network connect mynetwork <container>   # attach a running container
docker run --network mynetwork name:tag        # start already attached
docker network ls                              # list all networks

2.8Docker Compose

Docker Compose manages many containers at once from a single YAML file, instead of running and wiring them by hand.

Real apps are rarely one container — a server, a client, a database. Starting each with its own docker run and manually creating networks is tedious and error-prone. Compose declares the whole system in one docker-compose.yml: its services (containers), networks, and volumes (persistent storage). A few terms: services are the containers, networks connect them, volumes persist data.

💡 tip · what Compose replaces

Everything you did by hand in the two sections above — docker build per image, docker network create, and one docker run per container with the right --network and -p flags — collapses into one file and one command. That's the entire value proposition.

docker-compose.yml
version: '3'                    # compose file format; v3 is standard
services:
  server:
    build:
      context: .                # build context = current dir
      dockerfile: Dockerfile-server
    ports:
      - "8000:8080"             # host:container (needed only if host must reach it)
    networks:
      - mynetwork
  client:
    build:
      context: .
      dockerfile: Dockerfile-client
    depends_on:
      - server                  # start server before client
    networks:
      - mynetwork
networks:
  mynetwork:                     # the shared network both services join

Three commands drive it:

docker-compose build      # build all images from the yml
docker-compose up         # start every service (add -d to run detached)
docker-compose down       # stop containers, remove their networks, clean up

# custom filename:
docker-compose -f my-compose.yml up

Compose looks for docker-compose.yml by default; -f points it at another file. The depends_on key sets start order, and because both services join mynetwork they can talk to each other by service name — no manual networking.


2.9Interview & practice Q&A

What is the difference between a Docker image and a Docker container?

An image is a read-only template — code, dependencies, and a Dockerfile baked into shareable layers. A container is a running instance of an image, with a writable layer added on top. One image can spawn many containers, exactly like one class can instantiate many objects.

How is a container different from a virtual machine?

A container shares the host's kernel and packages only the app plus its dependencies, so it's small and fast to start but less isolated. A VM bundles a full guest operating system with its own kernel, so it's larger and slower but more strongly isolated. Containers win on speed and efficiency; VMs win when you need full OS isolation.

What does the OCI define and why does it matter?

The Open Container Initiative is an industry group that publishes three specifications — Runtime, Image, and Distribution. Together they make containers portable (self-contained) and interoperable (runnable on any compliant platform), so you're not locked to one vendor's tooling.

What is the difference between RUN and CMD in a Dockerfile?

RUN executes at build time and commits its result as a new image layer — use it for installing packages and preparing the image. CMD defines the default command that runs when the container starts via docker run. RUN shapes the image; CMD runs the app.

What does -p 8000:8080 mean, and which side is the host?

It maps host port 8000 to container port 8080. The format is always host:container — the left number is the port on your machine, the right is the port inside the container. A request to localhost:8000 is forwarded to :8080 inside the container.

Why might two running containers fail to communicate?

By default containers can only reach each other if they're attached to the same network. If they're on different networks (or one is on the default bridge and the other on a custom network), traffic won't flow. Create a custom network and attach both, or define them under one network in Compose.

What are Docker's three network modes?

bridge — the default, connecting the container to the default bridge network. none — disconnects the container from all networks. host — gives the container full access to the host's network stack. (There is no "channel" mode.)

What problem does Docker Compose solve?

It defines and runs multi-container applications from one YAML file. Instead of building images, creating networks, and running each container by hand, you declare services, networks, and volumes once and control them with docker-compose build/up/down.

Why can't you delete an image that still has containers?

Containers depend on their source image's read-only layers, so Docker refuses to remove an image while any container built from it exists. Remove the containers first with docker rm, then the image with docker image rm.

2.10Key takeaways

container = sealed app box shares host kernel image : container :: class : object OCI = runtime/image/distribution docker pull / run / ps / rm FROM WORKDIR RUN CMD -p host:container bridge / none / host same network to talk compose up / down

Section 3

Microexercises

Reading proves you recognise the idea; these prove you can use it. Attempt each before opening the walkthrough.

3.1Build it — a Dockerfile and its commands

// your task

Write a Dockerfile and the two commands to build and run it.

Expected result: running the container prints Hello World from Docker and exits.

Show the full walkthrough

Step 1 — pick the right instruction for each requirement. "Base it on" is FROM (and it must come first). "Working directory" is WORKDIR. "At build time" means RUN. "When the container starts" means CMD — not RUN, because startup output must fire at run time, not build time.

Step 2 — write the Dockerfile.

Dockerfile
# Sample Dockerfile
FROM ubuntu:latest
WORKDIR /app
RUN ["apt-get", "update"]
CMD echo Hello World from Docker

Step 3 — build and run. . is the build context and -t names the image.

docker build . -t hello-world-2
docker run hello-world-2
# -> Hello World from Docker

Why: RUN baked the package update into the image once; CMD is what actually executes each time you start a container from it — the build-time vs run-time split in action.

3.2Diagnose it — why can't the client reach the server?

// your task

A teammate starts two containers and the client cannot reach the server. Predict exactly why, then fix it with the fewest changes.

docker network create mynetwork

# server: attached to the custom network, port mapped
docker run -d -p 8000:8080 --network mynetwork server:latest

# client: started with no --network flag
docker run -d client:latest
# client tries to call the server by name and times out
Show the full walkthrough

Root cause: the client was started with no --network, so it landed on the default bridge network, while the server is on mynetwork. Containers can only communicate when they share a network.

Predicted failure: the client's request never reaches the server and the call times out (or fails to resolve the server's name). The server itself is healthy — this is purely a networking mismatch, so there's no crash and no obvious error in the server logs.

Fix A — start the client on the right network:

docker run -d --network mynetwork client:latest

Fix B — attach the client that's already running:

docker network connect mynetwork <client-container>

Why: this is the same-network rule as a reflex — the moment two containers "should" talk but don't, check that they're on one network before touching anything else. (In practice you'd sidestep the whole thing by declaring both under one network in a Compose file.)

CMD for startup, RUN for build docker build . -t name no --network = default bridge same network to communicate network connect = attach live