Build Release Notes with Git and Docker

Build a versioned release-notes site with Git and Docker.

Introduction

30 Second Summary

A website can look finished in your files while visitors still see an older version. When several changes overlap, one wrong edit can make it hard to tell what shipped or how to recover.

In this project, you will build a polished release-notes page with Git and serve it locally with Docker. You will combine feature work with a hotfix before replacing the old container with the merged release.

What You'll Build

Picture opening http://localhost:8080 to see a polished release page labeled Version 1.1.1: Feature + Hotfix after safely combining two competing changes.

By the end of this project, you'll have:

  • A local release-notes site that starts with a baseline release before displaying the merged feature and hotfix version at http://localhost:8080.
  • A repeatable Docker image workflow where you build versioned releases with the NGINX Official Image. You can inspect image tags, published ports, running containers, and web-server logs.
  • A readable Git history graph that shows parallel feature and hotfix work joined by a resolved merge conflict.
  • Secret Mission: Ship a deliberately bad release, reverse it safely with Git, and rebuild the recovered container.

Are there any prerequisites?

You need a supported Windows computer that can start Docker Desktop. This project assumes that Git for Windows and Visual Studio Code are already installed.

Before We Start

Before the hands-on work begins, lock in your goal: building a local versioned release-notes website. You will connect Git source history with Docker container lifecycle management so every release remains traceable and repeatable.

Verify Your Windows Toolchain

Your release workflow depends on a working local toolchain. Docker Desktop is installed without a confirmed connection to its engine.

This step verifies each installed tool from Windows PowerShell. You will also configure your Git identity before opening an empty workspace in Visual Studio Code.

In this step, get ready to:
  • Confirm Docker Desktop can communicate with its engine.
  • Verify Git plus your commit identity.
  • Verify Visual Studio Code before opening the project workspace.
Verify Docker Desktop and WSL

The Docker command-line tool sends instructions to the engine managed by Docker Desktop. Seeing both sides in one version check proves that the complete local Docker system is available.

  • Press the Windows key to open Windows search.
  • Type Docker Desktop into the search field.
  • Press Enter to start Docker Desktop.
  • Wait for Docker Desktop to finish starting.
  • Press the Windows key to return to Windows search.
  • Type Windows PowerShell into the search field.
  • Press Enter to open Windows PowerShell.

Before you run the check, do you expect PowerShell to reach only the Docker command-line tool or the running engine too?

  • Check the connection between the Docker command-line tool and its engine by running:
docker version

What Does This Command Check?

The Client section describes the Docker command-line tool. The Server section confirms that the command-line tool reached the running Docker engine.

✔️ I see Client and Server sections

That clears the first major dependency. Docker Desktop is running with an engine that can accept commands.

ⓧ I do not see a Server section

Docker Desktop commonly uses WSL as its Windows backend. The backend requires WSL version 2.1.5 or later.

  • Check the installed WSL version in PowerShell by running:
wsl --version

What Does This Command Check?

This command prints the installed WSL version. You can compare that number with Docker Desktop's minimum requirement.

✔️ I see version 2.1.5 or later

Your WSL version meets Docker Desktop's requirement. Continue below to retry the engine check.

ⓧ I see an older version

Updating WSL changes a Windows system component. An elevated PowerShell window gives the update the permission it needs.

  • Press the Windows key to open Windows search.
  • Type Windows PowerShell into the search field.
  • Right-click Windows PowerShell in the search results.
  • Select Run as administrator.
  • Approve the Windows permission prompt.
  • Update WSL from the elevated PowerShell window by running:
wsl --update
What Does This Command Do?

This command updates WSL to the latest version available for your Windows system. The newer backend gives Docker Desktop the supported foundation it expects.

  • Switch back to Docker Desktop.
  • Wait for Docker Desktop to finish starting.
  • Return to the PowerShell window from earlier.
  • Retry the Docker engine check by running:
docker version

What Should I See?

The output should now include separate Client and Server sections. The second section proves that Docker Desktop's engine is responding.

Still Missing the Server Section?

Make sure Docker Desktop has finished starting before you retry the command. A current WSL version alone does not confirm that the Docker engine is ready.

If the engine remains unavailable, help me diagnose why Docker Desktop has no Server section.

Verify Git and configure your identity

Git writes an author name plus an email address into each commit. Checking those values now prevents anonymous or incorrectly attributed history later.

  • Check the Git version plus the current identity values by running:
git --version
git config user.name
git config user.email

What Do These Commands Check?

  • The first command prints the installed Git version.
  • The second command prints the name Git records in commits.
  • The third command prints the email address Git records in commits.

✔️ I see a version, name, and email

Your Git installation already has a complete identity. Future commits can record who created them.

ⓧ A name or email is blank

Git needs both identity values before you create the project history. Use your own name plus your own email address in the example commands.

  • Replace Your Name in the first example with your name.
  • Replace you@example.com in the second example with your email address.
  • Set the missing Git identity values by running the adjusted commands:
git config --global user.name "Your Name"
git config --global user.email you@example.com

What Do These Commands Change?

The --global option stores this identity for repositories used by your Windows account. Each future commit can use the configured author details.

  • Verify the saved identity by running:
git config user.name
git config user.email

What Should I See?

PowerShell should print your name on the first line. It should print your email address on the second line.

Your commit identity is now ready. Git can attach these details to the release history you create.

Is a Git Identity Value Still Blank?

Check that you replaced both example values before running the configuration commands. Run the two configuration commands again if either value was skipped.

If a value still does not appear, help me fix my Git identity configuration.

Create and open the workspace

Visual Studio Code includes a command-line launcher that can open the folder currently selected in PowerShell. You will verify that launcher before creating the empty workspace.

  • Check the Visual Studio Code command-line launcher by running:
code --version

What Does This Command Check?

This command prints Visual Studio Code version information. Output here confirms that PowerShell can find the editor's command-line launcher.

You should see Visual Studio Code version information in PowerShell.

No Visual Studio Code Version?

Close PowerShell before opening a fresh window from Windows search. A fresh terminal session can pick up an installed command-line launcher.

If the command still produces no version information, help me make the Visual Studio Code command available in PowerShell.

  • Create the release-notes-site folder in PowerShell's current location by running:
New-Item -Path . -Name "release-notes-site" -ItemType "Directory"

What Does This Command Create?

PowerShell creates an empty directory named release-notes-site inside its current location. This directory becomes the home for the website plus its local Git history.

PowerShell should return details for the new release-notes-site directory.

Before you open the workspace, which folder do you expect Visual Studio Code's Explorer to show?

  • Move PowerShell into the workspace before opening that folder in Visual Studio Code by running:
Set-Location -Path ".\release-notes-site"
code .

What Do These Commands Do?

  • The first command changes PowerShell's current location to release-notes-site.
  • The second command opens the current folder in Visual Studio Code.

You should see release-notes-site as the open workspace in the Explorer sidebar. That completes your toolchain check with every required tool reachable from PowerShell.

Did the Workspace Fail to Open?

Check that PowerShell changed into release-notes-site before the editor command ran. The folder must exist in PowerShell's current location.

If Visual Studio Code opens a different folder, help me open release-notes-site from Windows PowerShell.

Your Windows toolchain is ready for the release workflow. Next up, you will package the first visible version of the site in a Docker image.

Package and Run the First Release

Your Windows toolchain is ready. Docker Desktop can now turn your local files into a running release.

Source files alone cannot prove repeatable packaging. You will bake the baseline page into a Docker image that includes NGINX. You will launch that image as a named container.

In this step, get ready to:
  • Create the three baseline release files.
  • Build the tagged release image.
  • Serve the baseline page from a named container.
Create the baseline release files

A build context is the folder Docker can read while building an image. Your release-notes-site workspace becomes that context.

The Dockerfile defines the package. The .dockerignore keeps the repository history outside that package.

Why NGINX for this release?

NGINX serves static HTML directly. A Node.js server would add package setup that this release does not need.

This approach keeps your attention on the image lifecycle. Every browser result comes from the file baked into the image.

  • In the VS Code file list from earlier, create a file named Dockerfile inside release-notes-site.
  • Paste the image instructions into Dockerfile using the code below:
FROM nginx:1.30.6-alpine3.24
COPY index.html /usr/share/nginx/html/index.html

What Does This Dockerfile Do?

  • The FROM instruction pins the release to the nginx:1.30.6-alpine3.24 base image.
  • The COPY instruction places your page in NGINX's static-content directory.
  • Save Dockerfile.
  • Confirm that Dockerfile appears in the VS Code file list.

Dockerfile Missing or Named Differently?

Confirm that the filename starts with a capital D. Remove any file extension that VS Code added.

Ask for help checking the filename if it still looks different: help me verify that my Dockerfile has the correct name in VS Code on Windows

  • In the same VS Code file list, create .dockerignore inside release-notes-site.
  • Exclude the Git history from the build context using the code below:
.git

What Does This File Do?

The .git entry removes the repository metadata from the context sent to the builder. The release image only needs the page that NGINX serves.

  • Save .dockerignore.
  • Confirm that .dockerignore appears beside Dockerfile in the VS Code file list.

Cannot See the Ignore File?

Check that the filename begins with a full stop. Confirm that it does not end with .txt.

Ask for help checking the hidden filename: help me fix a .dockerignore file that is missing or has a .txt extension in VS Code on Windows

  • In the same VS Code file list, create index.html inside release-notes-site.
  • Add the baseline page structure to index.html using the code below:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Release Notes</title>
  <style>
  </style>
</head>
<body>
  <main>
    <p class="eyebrow">Container Release Notes</p>
    <h1>Version 1.0: Baseline</h1>
    <p class="summary">The first containerized release is running successfully.</p>
    <ul>
      <li>Created the first release page.</li>
      <li>Packaged the site with NGINX.</li>
      <li>Published the container on port 8080.</li>
    </ul>
    <span class="status">Stable</span>
  </main>
</body>
</html>

What Does This Structure Create?

  • The metadata gives the browser a page title plus responsive sizing information.
  • The main element groups the visible release information.
  • The heading identifies this package as Version 1.0: Baseline.
  • The list records what the first release demonstrates.
  • Save index.html.
  • Confirm that VS Code shows the baseline heading plus three release items in the file.

HTML Structure Looks Incomplete?

Confirm that every opening HTML tag has its matching closing tag. Keep the blank line between <style> and </style> for the styles you add next.

Ask for help comparing the structure: help me compare my index.html structure with the baseline release page

The first style group controls the page canvas. It also creates the centered card that holds the release notes.

  • In index.html, find the blank line after <style>.
  • Paste the following styles on that blank line:
    :root {
      color-scheme: dark;
      font-family: Inter, Segoe UI, sans-serif;
      background: #0f172a;
      color: #e2e8f0;
    }

    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: radial-gradient(circle at top, #1e3a8a, #0f172a 55%);
    }

    main {
      width: min(720px, calc(100% - 3rem));
      padding: 2.5rem;
      border: 1px solid #334155;
      border-radius: 1.25rem;
      background: rgba(15, 23, 42, 0.88);
      box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
    }

How Does the Page Foundation Work?

  • The :root rules establish the dark palette plus the page font.
  • The body rules center the release card against a blue gradient.
  • The main rules create a responsive card with spacing plus depth.
  • Save index.html.
  • Confirm that the :root, body, and main selectors sit inside the <style> element.

Styles Outside the Style Element?

Move the selectors above </style> if they appear elsewhere. Check that every selector ends with a closing brace.

Ask for help placing the styles: help me place the page foundation CSS inside the style element in index.html

The next style group creates the release hierarchy. The heading becomes the main focal point.

  • Find the closing brace for the main selector in index.html.
  • Paste the following styles below that closing brace:
    .eyebrow {
      margin: 0 0 0.75rem;
      color: #38bdf8;
      font-size: 0.8rem;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    h1 {
      margin: 0;
      font-size: clamp(2rem, 7vw, 4rem);
      line-height: 1.05;
    }

    .summary {
      margin: 1rem 0 2rem;
      color: #cbd5e1;
      font-size: 1.1rem;
    }

How Does the Release Hierarchy Work?

  • The .eyebrow rules turn the small label into an uppercase release marker.
  • The h1 rules scale the version heading with the browser width.
  • The .summary rules separate the release description from the change list.
  • Save index.html.
  • Confirm that the three new selectors appear below the main selector.

Release Text Styles Misaligned?

Check that .eyebrow includes its leading full stop. Confirm that .summary uses the same spelling as the HTML class.

Ask for help matching the selectors: help me match the eyebrow and summary CSS selectors to their HTML classes

The final style group separates each release item. It also turns the stability state into a visible badge.

  • Find the closing brace for the .summary selector in index.html.
  • Paste the remaining styles below that closing brace:
    ul {
      display: grid;
      gap: 0.8rem;
      margin: 0;
      padding-left: 1.25rem;
    }

    li::marker {
      color: #38bdf8;
    }

    .status {
      display: inline-block;
      margin-top: 2rem;
      padding: 0.5rem 0.8rem;
      border-radius: 999px;
      background: #14532d;
      color: #bbf7d0;
      font-weight: 700;
    }

How Does the Release Detail Work?

  • The ul rules add consistent space between release items.
  • The li::marker rules connect each bullet to the blue accent colour.
  • The .status rules present the stability state as a green pill.
  • Save index.html.
  • Confirm that ul, li::marker, and .status appear before </style>.

List or Status Styles Missing?

Check the two colons in li::marker. Confirm that the HTML badge still uses class="status".

Ask for help checking the final selectors: help me troubleshoot the list marker and status badge styles in index.html

✔️ Awesome, I've got everything!

Your three release files are complete. Double-check that each file is saved.

ⓧ I'd like to double check the full code

.git
FROM nginx:1.30.6-alpine3.24
COPY index.html /usr/share/nginx/html/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Release Notes</title>
  <style>
    :root {
      color-scheme: dark;
      font-family: Inter, Segoe UI, sans-serif;
      background: #0f172a;
      color: #e2e8f0;
    }

    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: radial-gradient(circle at top, #1e3a8a, #0f172a 55%);
    }

    main {
      width: min(720px, calc(100% - 3rem));
      padding: 2.5rem;
      border: 1px solid #334155;
      border-radius: 1.25rem;
      background: rgba(15, 23, 42, 0.88);
      box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
    }

    .eyebrow {
      margin: 0 0 0.75rem;
      color: #38bdf8;
      font-size: 0.8rem;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    h1 {
      margin: 0;
      font-size: clamp(2rem, 7vw, 4rem);
      line-height: 1.05;
    }

    .summary {
      margin: 1rem 0 2rem;
      color: #cbd5e1;
      font-size: 1.1rem;
    }

    ul {
      display: grid;
      gap: 0.8rem;
      margin: 0;
      padding-left: 1.25rem;
    }

    li::marker {
      color: #38bdf8;
    }

    .status {
      display: inline-block;
      margin-top: 2rem;
      padding: 0.5rem 0.8rem;
      border-radius: 999px;
      background: #14532d;
      color: #bbf7d0;
      font-weight: 700;
    }
  </style>
</head>
<body>
  <main>
    <p class="eyebrow">Container Release Notes</p>
    <h1>Version 1.0: Baseline</h1>
    <p class="summary">The first containerized release is running successfully.</p>
    <ul>
      <li>Created the first release page.</li>
      <li>Packaged the site with NGINX.</li>
      <li>Published the container on port 8080.</li>
    </ul>
    <span class="status">Stable</span>
  </main>
</body>
</html>
Build the versioned image

Building reads the Dockerfile plus the files available in the current context. The resulting tag gives this package a release identity you can inspect locally.

The first build can take longer while Docker downloads the pinned NGINX base image. Later builds can reuse local layers.

  • Switch back to the PowerShell window from earlier.
  • Build the baseline image from the current release-notes-site workspace by running:
docker build -t release-notes:1.0 .

What Does This Command Do?

  • The -t option assigns the image name plus the 1.0 tag.
  • The final . selects your current release-notes-site folder as the build context.
  • Docker follows the Dockerfile to copy the baseline page into the NGINX image.

Before you inspect the image list, predict which repository name plus tag the build recorded.

  • Inspect the new local image by running:
docker image ls release-notes

What Does the Image List Show?

The command filters the local image list to the release-notes repository. Its row identifies the tag plus the locally stored image.

You should see release-notes as the repository. The tag should be 1.0.

That package is ready: your baseline page now exists inside a versioned local image.

Image Missing or Build Failed?

Confirm that PowerShell is still located in release-notes-site. Check that Dockerfile plus index.html appear in that folder.

If the base image cannot download, confirm that Docker Desktop is still running. Retry after its engine is available.

Ask for help with the build output: help me troubleshoot why my release-notes:1.0 Docker image did not build on Windows

Run and verify the container

Port publishing connects a port on Windows to a port inside the container. This release maps browser traffic on port 8080 to NGINX on port 80.

  • Create the named background container from the baseline image by running:
docker run --detach --name release-notes --publish 8080:80 release-notes:1.0

What Does This Command Do?

  • The --detach option keeps the container running in the background.
  • The --name option assigns the reusable name release-notes.
  • The --publish option connects Windows port 8080 to container port 80.
  • The final argument selects the release-notes:1.0 image.

Before you check the running state, predict which container name plus port mapping you expect to find.

  • List the running containers by running:
docker ps

What Does the Container List Prove?

The running-container list connects the container name to its image. It also shows the published host port.

You should see a running container named release-notes. Its image should be release-notes:1.0.

  • Use your web browser to open http://localhost:8080.

You should see the dark release card with the heading Version 1.0: Baseline. The page should also show the green Stable badge.

Your first containerized release is live. The browser is now serving the exact page stored inside the tagged image.

  • Refresh http://localhost:8080 to send another request to NGINX.

Before you inspect the logs, predict what evidence the browser request leaves behind.

  • Fetch the container logs by running:
docker logs release-notes

What Do the Logs Prove?

The logs come from the NGINX process inside release-notes. A request entry connects your browser refresh to activity inside the running container.

You should see request activity after refreshing the page. That output proves the browser reached the web server inside the container.

Page Missing or Port Unavailable?

Confirm that docker ps lists release-notes as running. If port 8080 is already allocated, stop the local process or container using it.

Keep port 8080 for this project. Its verification URL depends on that mapping.

Ask for help with the container state: help me troubleshoot why my release-notes container is not serving http://localhost:8080 on Windows

Your baseline release is packaged plus running from a versioned image. Next, you will record it in Git before creating parallel release changes.

Create Branches and Trigger a Merge Conflict

Your baseline Docker release is already running at http://localhost:8080. Its source now needs a history that captures how each release changes.

Git branches isolate parallel work. When two branches change the same release heading, Git stops at a merge conflict because it cannot safely select the intended version.

In this step, get ready to:
  • Initialize the repository and commit the baseline release.
  • Record competing feature and hotfix headings on separate branches.
  • Merge both branches until Git stops at the planned conflict.
Initialize the repository and commit the baseline

A working tree holds the files you can edit. The staging area selects the exact file versions that become the next commit.

  • Switch back to the PowerShell window from earlier.
  • Initialize the repository with main as its first branch by running the first command below:
  • Inspect the new repository by running the second command below:
git init -b main
git status

What Did Git Create?

  • The first command creates Git metadata inside the existing release-notes-site workspace.
  • The -b main option names the initial branch main.
  • The status output lists index.html, Dockerfile, and .dockerignore as untracked files.
  • Stage the three project files by running the first command below:
  • Record the baseline snapshot by running the second command below:
  • Confirm the repository has no remaining file changes by running the third command below:
git add index.html Dockerfile .dockerignore
git commit -m "Create containerized release notes"
git status

How Did the Baseline Move?

  • The first command copies the current file contents into the staging area.
  • The second command records those staged contents with the message Create containerized release notes.
  • The final status check should list no modified files or untracked files.

Your first clean checkpoint is ready. The complete baseline release now has a permanent place in Git history.

Create and commit the feature branch

A feature branch gives one change its own line of development. The baseline on main remains unchanged while you record the feature heading.

  • Create the feature branch by running this command:
git switch -c feature/release-banner

Why Isolate the Feature?

The new branch starts from the baseline commit. New commits now advance feature/release-banner without moving main.

  • Switch back to VS Code.
  • Select index.html in the Explorer sidebar.
  • Replace the heading text Version 1.0: Baseline with Version 1.1: Feature release.
  • Save index.html.
  • Return to PowerShell and inspect the feature change by running:
git status

What Changed?

Git should identify index.html as modified. The edit remains in the working tree until you stage it.

  • Stage the feature heading by running the first command below:
  • Commit the feature heading by running the second command below:
  • Confirm the branch has no remaining file changes by running the third command below:
git add index.html
git commit -m "Add feature release banner"
git status

What Does the Feature Commit Prove?

  • The commit stores the feature heading on feature/release-banner.
  • The status check should list no modified files.
  • You now have one complete branch that Git can merge later.
Create the hotfix branch and trigger the conflict

The hotfix must also start from the baseline on main. This creates two histories that propose different text for the same heading.

  • Return to main by running the first command below:
  • Create the hotfix branch by running the second command below:
git switch main
git switch -c hotfix/release-banner

Why Branch from Main Again?

Switching to main restores the baseline heading before the hotfix branch is created. The hotfix therefore develops independently from the feature.

  • Switch back to VS Code.
  • Select index.html in the Explorer sidebar.
  • Replace the heading text Version 1.0: Baseline with Version 1.0.1: Hotfix release.
  • Save index.html.
  • Return to PowerShell and inspect the hotfix change by running:
git status

What Changed on the Hotfix?

Git should identify index.html as modified again. This working tree contains the hotfix heading.

  • Stage the hotfix heading by running the first command below:
  • Commit the hotfix heading by running the second command below:
  • Confirm the hotfix branch has no remaining file changes by running the third command below:
git add index.html
git commit -m "Add hotfix release banner"
git status

What Does the Hotfix Commit Prove?

  • The commit stores the hotfix heading on hotfix/release-banner.
  • The status check should list no modified files.
  • You now have two complete branches with competing release headings.
  • Return to main by running the first command below:
  • Bring the feature commit into main by running the second command below:
git switch main
git merge feature/release-banner

What Did the First Merge Do?

The feature history joins main without competing edits. The working file now contains Version 1.1: Feature release.

Before you run the next merge, do you think Git can choose between the feature heading and the hotfix heading?

  • Attempt to merge the hotfix by running the first command below:
  • Inspect the resulting repository state by running the second command below:
git merge hotfix/release-banner
git status

What Did Git Find?

  • The merge stops because both branches replaced the same heading with different text.
  • The status output identifies index.html as unmerged.
  • Git leaves the decision in your working tree instead of guessing which release is correct.
  • Switch back to VS Code.
  • Select index.html in the Explorer sidebar.

You should see <<<<<<<, =======, and >>>>>>> around the competing headings. The feature heading appears above the separator while the hotfix heading appears below it.

Conflict Markers Missing?

Confirm that both branches changed the same <h1> line before their commits. Changes on separate lines allow Git to complete the merge automatically.

If your merge completed without stopping, help me safely recreate the planned Git conflict.

✔️ Awesome, I've got everything!

The planned conflict is in place. The next step begins from this unresolved merge.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Release Notes</title>
  <style>
    :root {
      color-scheme: dark;
      font-family: Inter, Segoe UI, sans-serif;
      background: #0f172a;
      color: #e2e8f0;
    }

    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: radial-gradient(circle at top, #1e3a8a, #0f172a 55%);
    }

    main {
      width: min(720px, calc(100% - 3rem));
      padding: 2.5rem;
      border: 1px solid #334155;
      border-radius: 1.25rem;
      background: rgba(15, 23, 42, 0.88);
      box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
    }

    .eyebrow {
      margin: 0 0 0.75rem;
      color: #38bdf8;
      font-size: 0.8rem;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    h1 {
      margin: 0;
      font-size: clamp(2rem, 7vw, 4rem);
      line-height: 1.05;
    }

    .summary {
      margin: 1rem 0 2rem;
      color: #cbd5e1;
      font-size: 1.1rem;
    }

    ul {
      display: grid;
      gap: 0.8rem;
      margin: 0;
      padding-left: 1.25rem;
    }

    li::marker {
      color: #38bdf8;
    }

    .status {
      display: inline-block;
      margin-top: 2rem;
      padding: 0.5rem 0.8rem;
      border-radius: 999px;
      background: #14532d;
      color: #bbf7d0;
      font-weight: 700;
    }
  </style>
</head>
<body>
  <main>
    <p class="eyebrow">Container Release Notes</p>
<<<<<<< HEAD
    <h1>Version 1.1: Feature release</h1>
=======
    <h1>Version 1.0.1: Hotfix release</h1>
>>>>>>> hotfix/release-banner
    <p class="summary">The first containerized release is running successfully.</p>
    <ul>
      <li>Created the first release page.</li>
      <li>Packaged the site with NGINX.</li>
      <li>Published the container on port 8080.</li>
    </ul>
    <span class="status">Stable</span>
  </main>
</body>
</html>

You have created a real branch conflict without losing either change. Next, you will turn those competing histories into one intentional release.

Resolve the Conflict and Inspect the History

The feature release and hotfix now meet in a stopped Git merge. Git has left the final release decision in your hands.

Your job is to combine both changes into one intentional release. You will then compare the completed history with the existing container in Docker Desktop.

In this step, get ready to:
  • Inspect the stopped merge and identify both competing headings.
  • Resolve the competing release lines into one committed page.
  • Prove the completed source history remains separate from the running container.
Inspect the stopped merge

Git records unresolved files in the working tree. Its diff shows the exact lines that require your decision.

  • Switch back to PowerShell from earlier.
  • Inspect the stopped merge by running these commands:
git status
git diff

What do these checks show?

  • The git status command identifies index.html as unresolved in the working tree.
  • The git diff command displays both headings inside the conflict markers.
  • The stopped merge protects both versions until you choose the final content.
  • Switch back to the index.html tab in VS Code.
  • Find the conflict section beginning with <<<<<<<.
  • Read the feature heading above =======.
  • Read the hotfix heading above >>>>>>>.

The upper section contains the feature release. The lower section contains the hotfix release.

Cannot find the conflict markers?

  • Confirm that git status still identifies index.html as unresolved.
  • Return to the existing index.html tab if another file is active.

Ask for help to find the stopped merge conflict in index.html.

Resolve the release content

A useful resolution preserves the intent of both branches. This release needs the feature banner plus the production hotfix.

  • Select the complete <main> block in index.html.
  • Replace the selected block with this resolved release content:
  <main>
    <p class="eyebrow">Container Release Notes</p>
    <h1>Version 1.1.1: Feature + Hotfix</h1>
    <p class="summary">The feature release and production hotfix are now combined.</p>
    <ul>
      <li>Added a clearer release banner.</li>
      <li>Applied the urgent production wording fix.</li>
      <li>Rebuilt the site as an immutable Docker image.</li>
    </ul>
    <span class="status">Stable</span>
  </main>

What does this resolution preserve?

  • The heading combines the feature release with the hotfix in one version.
  • The summary records that both streams of work now belong to the same release.
  • The list documents the banner change plus the production fix.
  • Save index.html.
  • Confirm that the heading reads Version 1.1.1: Feature + Hotfix.
  • Confirm that all three conflict marker lines are gone.

Still see conflict markers?

  • Check that your selection began at <main>.
  • Check that your selection ended at </main>.
  • Compare the resolved block with the full file below.

Ask for help to remove the remaining conflict markers safely.

✔️ Awesome, I've got everything!

Your resolved release content is ready. Make sure index.html is saved.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Release Notes</title>
  <style>
    :root {
      color-scheme: dark;
      font-family: Inter, Segoe UI, sans-serif;
      background: #0f172a;
      color: #e2e8f0;
    }

    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: radial-gradient(circle at top, #1e3a8a, #0f172a 55%);
    }

    main {
      width: min(720px, calc(100% - 3rem));
      padding: 2.5rem;
      border: 1px solid #334155;
      border-radius: 1.25rem;
      background: rgba(15, 23, 42, 0.88);
      box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
    }

    .eyebrow {
      margin: 0 0 0.75rem;
      color: #38bdf8;
      font-size: 0.8rem;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    h1 {
      margin: 0;
      font-size: clamp(2rem, 7vw, 4rem);
      line-height: 1.05;
    }

    .summary {
      margin: 1rem 0 2rem;
      color: #cbd5e1;
      font-size: 1.1rem;
    }

    ul {
      display: grid;
      gap: 0.8rem;
      margin: 0;
      padding-left: 1.25rem;
    }

    li::marker {
      color: #38bdf8;
    }

    .status {
      display: inline-block;
      margin-top: 2rem;
      padding: 0.5rem 0.8rem;
      border-radius: 999px;
      background: #14532d;
      color: #bbf7d0;
      font-weight: 700;
    }
  </style>
</head>
<body>
  <main>
    <p class="eyebrow">Container Release Notes</p>
    <h1>Version 1.1.1: Feature + Hotfix</h1>
    <p class="summary">The feature release and production hotfix are now combined.</p>
    <ul>
      <li>Added a clearer release banner.</li>
      <li>Applied the urgent production wording fix.</li>
      <li>Rebuilt the site as an immutable Docker image.</li>
    </ul>
    <span class="status">Stable</span>
  </main>
</body>
</html>

Git finishes the stopped merge after you add the resolved file to the staging area and create the merge commit.

  • Record the conflict resolution by running these commands:
git add index.html
git commit -m "Resolve release banner conflict"

What did these commands record?

  • The add command marks index.html as resolved in the staging area.
  • The commit command completes the merge with a message that documents your decision.
  • Both branch histories now meet in the new commit on main.

Before you check the repository, do you expect Git to report another unresolved path?

  • Verify that the merge is complete by running this command:
git status

What should I see?

Git reports a clean repository with no unresolved paths. The merge is now complete on main.

Does Git still report a conflict?

  • Confirm that index.html contains no conflict markers.
  • Save the resolved file before adding it again.
  • Repeat the add command before repeating the commit command.

Ask for help to finish the stopped Git merge after resolving index.html.

Compare the history with the running site

The repository now records your resolved release. The browser gives you a separate test of what the existing container serves.

Before you run the history command, what shape do you expect the feature and hotfix paths to form?

  • Display the completed branch topology by running this command:
git log --oneline --graph --all --decorate

How does the graph read?

  • The --oneline option keeps each commit compact.
  • The --graph option draws the diverging paths plus the merge.
  • The --all option includes every local branch.
  • The --decorate option labels the branch references.
  • You can trace the baseline into separate feature and hotfix commits before both paths join at the resolution commit.

Does the graph look incomplete?

  • Check that every option shown in the command above is present.
  • Confirm that the resolution commit appears at the top of the history.

Ask for help to interpret the branch and merge lines in my Git graph.

Before you refresh the site, do you expect the running page to show the resolved heading or the baseline heading?

  • Load the existing site by entering http://localhost:8080 in your browser address bar.

You will still see Version 1.0: Baseline. This stale page is the intended result of the check.

Why is the baseline still running?

The existing container was created from release-notes:1.0. Your Git commit updates the source files on disk.

A running container keeps using the image that created it. The browser proves that committed source and running containers have separate lifecycles.

Did the site fail to load?

  • Confirm that Docker Desktop remains running.
  • Confirm that the existing release-notes container is running.
  • Reload http://localhost:8080 after confirming the container state.

Ask for help to restore access to the local release-notes container.

That is the conflict resolved: both branches now meet in a readable history. Next, you will rebuild the merged source and replace the baseline container.

Rebuild and Replace the Container

Your Git history now joins the feature release with the hotfix. The browser still shows the baseline because the running container holds the files baked into the earlier release.

The old container came from release-notes:1.0. You will build a new Docker image from the merged source before replacing that container.

In this step, get ready to:
  • Build the merged source as release-notes:1.1.1.
  • Replace the baseline container with the merged release.
  • Verify the replacement across its page, runtime state, and server logs.
Build the merged image

A Docker image captures your source at build time. A new tag preserves the baseline release while giving the merged release its own identity.

  • Use the current Dockerfile to create release-notes:1.1.1 by running this command in PowerShell:
docker build -t release-notes:1.1.1 .

What does this build create?

  • The -t flag assigns the release-notes:1.1.1 tag to the new image.
  • The final . supplies the current release-notes-site folder as the build context.
  • The existing Dockerfile copies the merged index.html into the image.

When the build finishes, PowerShell shows that the image was exported with the release-notes:1.1.1 tag.

  • List the project images by running this command:
docker image ls release-notes

What does this list confirm?

The image list filters your local images to the release-notes repository. Separate tags let you identify the baseline build and the merged build.

You will see both release-notes:1.0 and release-notes:1.1.1 in the output. That confirms the new release is packaged without removing the baseline image.

Build missing from the image list?

Check that PowerShell is still inside the release-notes-site folder. Confirm that Dockerfile and index.html remain visible in the VS Code Explorer.

Confirm Docker Desktop is still running before trying the build again.

Help me troubleshoot why my merged Docker image is missing from the local image list.

Replace the baseline container

Stopping and removing the baseline container can feel destructive. Your two local images remain available throughout this replacement.

  • Stop the baseline container by running this command:
docker stop release-notes

What does stopping preserve?

Docker stops the process inside the named container. The container still exists until you remove it.

PowerShell prints release-notes when the container stops successfully.

Container could not be stopped?

Confirm Docker Desktop is still running. A missing container means its lifecycle changed outside this project flow.

Help me find out why Docker cannot stop my release-notes container.

  • Remove the stopped baseline container by running this command:
docker rm release-notes

Why remove the stopped container?

Removing the stopped container releases the release-notes name. The replacement can now use that same name.

PowerShell prints release-notes again when removal succeeds.

  • Create the replacement container from release-notes:1.1.1 by running this command:
docker run --detach --name release-notes --publish 8080:80 release-notes:1.1.1

How is the replacement configured?

  • The --detach flag runs the web server in the background.
  • The --name flag assigns the reusable release-notes container name.
  • The --publish flag maps host port 8080 to container port 80.
  • The final image reference creates the container from the merged release.

PowerShell prints a container ID when Docker creates the replacement. The new container now owns the release-notes name.

Replacement container did not start?

A name conflict means the earlier container still exists. Confirm that the removal command completed before trying again.

A port conflict means another local process or container is using port 8080. Stop that process before continuing because this project uses the same browser URL for every release.

Help me troubleshoot why the release-notes replacement container cannot start on port 8080.

Verify the replacement release

The replacement needs three matching signals. Its running state, browser page, and web-server logs should all point to the merged release.

Before you run the final checks, which image tag and page heading do you expect to see?

  • Inspect the running container by running this command:
docker ps

What does the container list prove?

This lists the containers that are currently running. The matching image tag identifies the build used to create each container.

You will see the release-notes container using release-notes:1.1.1. Its port mapping publishes host port 8080 to container port 80.

  • Switch back to the browser tab showing http://localhost:8080.
  • Refresh the page.

You will now see Version 1.1.1: Feature + Hotfix above the combined summary. The three merged release items appear below it.

That is the full release loop working. Your merged source is now packaged into the image that serves the live page.

  • Read the web-server logs after that browser refresh by running this command:
docker logs release-notes

What do the logs connect?

The logs come from the web-server process inside the replacement container. A recent request entry connects your browser refresh to that running process.

You will see a recent request in the output. This confirms that the updated container handled the browser visit.

Page or logs still look stale?

Confirm the running container uses release-notes:1.1.1. A baseline image tag means the replacement command used the earlier release.

Refresh the browser before reading the logs. The refresh creates the request you are looking for.

Help me troubleshoot why my replacement container still shows the baseline page or no recent browser request.

Secret mission

Recover from a Bad Release

Ship a deliberately broken release without hiding the mistake from your project history. Reverse the bad commit safely. Then rebuild the recovered site in a replacement container.

Clean Up Your Resources

Clean Up Your Resources

Choose whether to keep your local resources ready, pause the running container, or remove the project entirely. These local Docker resources incur no usage charges.

Resources you used:

  • The local Git repository stored in release-notes-site with its auditable release history.
  • The running release-notes container created from release-notes:recovered.
  • The local project images tagged release-notes:1.0, release-notes:1.1.1, and release-notes:recovered.
  • The local NGINX base image tagged nginx:1.30.6-alpine3.24.

Keep everything running

No action is needed. Choose this option if you want the recovered site ready for another demonstration.

  • Leave the release-notes container running.
  • Visit http://localhost:8080 whenever you want to demonstrate the recovered release.
  • Keep the three project images so you can recreate each release.
  • Keep the release-notes-site repository so its commit history remains available.

Pause - I'll come back to this later

Shut down the running container to free its local system resources. Its configuration remains available for a later restart.

  • Switch back to the PowerShell window from earlier.
  • Stop the running release-notes container by running this command:
docker stop release-notes

What changed?

  • Docker stops the running container while preserving it for a later restart.
  • The command prints release-notes when the stop completes.
  • Confirm the stopped container remains available by running this command:
docker ps -a

What should you see?

  • You should still see a row for release-notes.
  • That row confirms the container still exists for a later restart.
  • Restart the existing container when you return by running this command:
docker start release-notes

How does the site return?

  • Docker starts the same container with its saved port configuration.
  • The recovered site becomes available again at http://localhost:8080.

Delete - I don't want to use this again

Remove every project resource when you are finished with this release workflow.

Deleting local work can feel final. The steps below target only this project's resources.

Deletion removes your local history

The final folder deletion removes the Git graph you built. That history cannot be inspected after the folder is gone.

Container cleanup:

  • Switch back to the PowerShell window from earlier.
  • Remove the running release-notes container by running this command:
docker rm --force release-notes

What gets removed?

  • The --force flag stops the running container before removing it.
  • Removing the container also releases host port 8080.
  • Confirm the container is gone by running this command:
docker ps -a

What should you see?

  • You should see no container named release-notes in the list.

Project image cleanup:

  • Remove the three project image tags by running these commands:
docker image rm release-notes:1.0
docker image rm release-notes:1.1.1
docker image rm release-notes:recovered

What happens to the releases?

  • Docker removes each local project image tag.
  • The command output identifies each tag as it is removed.
  • Confirm the project images are gone by running this command:
docker image ls release-notes

What should you see?

  • You should see no image rows for the release-notes repository.

Base image cleanup:

  • Leave nginx:1.30.6-alpine3.24 in place if another local project uses it.
  • Remove the base image when this project is its only local user by running this command:
docker image rm nginx:1.30.6-alpine3.24

Why is this optional?

  • Other local projects may depend on the same NGINX base image.
  • Removing an unused copy frees additional disk space.

Repository cleanup:

  • Close the release-notes-site workspace in Visual Studio Code.
  • Close the PowerShell window from earlier.
  • Use Windows search to open File Explorer.
  • Open the location that contains the release-notes-site folder.
  • Delete the release-notes-site folder.
  • Confirm that release-notes-site no longer appears in that location.

Nice Work!

Nice Work!

You made it. Your recovered release-notes site now runs from a Docker image backed by an auditable Git history.

You've learned how to:

  • Build a custom NGINX-based Docker image from a Dockerfile. Use its build context to package a static release-notes site.
  • Control the container lifecycle by publishing port 8080. Inspect local images. Inspect running containers. Read web-server logs. Replace an older container with a rebuilt release.
  • Create a readable Git history with commits, branches, and merges. Resolve a planned merge conflict between feature work and a hotfix. Prove that committed source leaves an existing container unchanged.
  • Secret Mission: Reverse a deliberately bad release with an auditable revert commit. Rebuild the recovered site in the release-notes:recovered image.

You now have a repeatable local workflow for moving a source change into a versioned replacement container.

Ready to quiz yourself?