Build a Secure CI/CD Pipeline
Build, test, and publish a Docker image with GitHub Actions and GHCR.
Introduction
30 Second Summary
A small code change can pass every local check and still fail when someone else tries to run it. Without a shared checkpoint, that failure reaches the next person before anyone sees it coming.
In this project, you will turn each DentalFlow code change into a visible CI/CD pipeline that validates the app using GitHub Actions and Docker. A successful main run publishes a tested container image while the live AWS environment stays untouched.
What You'll Build
Picture a DentalFlow change moving through a green Validate container run into a traceable package in GitHub Container Registry.
By the end of this project, you'll have:
- A visible quality gate that reports a green or red Validate container result for every pushed commit and every pull request targeting main.
- Useful failure logs that reveal why the DentalFlow app could not start during a container smoke test.
- A pullable container package with an immutable full commit-SHA tag and a convenient latest tag for demonstrations.
- Secret Mission: Protect main with a branch rule that blocks merges until Validate container succeeds.
Are there any prerequisites?
This project assumes admin access to keusuanl/DentalFlow plus an existing local clone opened in Visual Studio Code through Debian on Windows Subsystem for Linux. Git and Docker must already be working, so there is nothing new to install.
Before We Start
Before any files change, take a moment to define the boundary of this CI/CD project. You are committing to validate every change and publish a container image while keeping the live DentalFlow environment untouched.
Prepare a Safe CI Working Branch
Automation files influence every future push to DentalFlow. A mistake in a CI/CD workflow can affect every later commit.
A clean working tree gives GitHub Actions a trustworthy starting point. A dedicated branch keeps this setup isolated from main.
You'll verify Git from Debian on Windows Subsystem for Linux (WSL). You'll also confirm that Docker can reach its engine.
In this step, get ready to:
- Open the local DentalFlow repository in Visual Studio Code through WSL.
- Verify the local development foundation.
- Create feature/ci-container-pipeline as the isolated working branch.
Use the repository's Linux context
Visual Studio Code can attach directly to the Debian environment through WSL. Opening the repository from its terminal keeps the editor aligned with the same Linux files.
- Return to the Debian WSL terminal from earlier.
- Enter the existing local DentalFlow repository using the path where you cloned it.
- Launch the current repository in Visual Studio Code by running this command:
code .
What does this command do?
This command starts Visual Studio Code from the current Debian directory. The editor uses the same Linux files that your terminal sees.
- Confirm the Visual Studio Code status area identifies a WSL connection.
Visual Studio Code not connected to WSL?
- Close the Visual Studio Code window that lacks a WSL connection.
- Return to the Debian WSL terminal from earlier.
- Run the repository-opening command again from inside DentalFlow.
Help me connect the DentalFlow repository to WSL.
Your editor now shares the repository context with Debian. That connection keeps the upcoming workflow files inside the correct copy of DentalFlow.
Verify the local foundation
The pipeline depends on the repository plus a reachable container engine. These checks expose a wrong folder or disconnected service before automation enters the picture.
- Confirm Git is available by running this command:
git --version
What does this check prove?
This check confirms that the Debian shell can find Git. The output identifies the installed release.
You should see a Git version in the terminal.
Git check not running?
- Return to the Debian WSL terminal from earlier.
- Repeat the Git check there.
Help me restore Git access in Debian WSL.
- Confirm the Docker client can reach the Docker server by running this command:
docker version
What does this check prove?
This check asks the Docker client for its details. It also contacts the Docker server that builds and runs containers.
You should see both Client and Server sections.
Docker server details missing?
- Confirm Docker is running in the environment you prepared before this project.
- Repeat the Docker check from the Debian WSL terminal.
Help me reconnect Docker to Debian WSL.
- Inspect the repository's configured remotes by running this command:
git remote -v
What does this check prove?
A remote connects your local repository to its hosted copy. The fetch and push entries reveal which repository receives your branch.
You should see origin entries whose URL contains keusuanl/DentalFlow.
Remote points somewhere else?
- Confirm the terminal is inside the local DentalFlow repository.
- Pause before changing the remote if origin points to another repository.
Help me verify the correct DentalFlow remote.
- Inspect the current branch plus working-tree state by running this command:
git status --short --branch
What does this check prove?
The short status displays the current branch at the top. Any changed or untracked paths appear below it.
You should see a branch header. No changed file entries should follow it.
Working tree not clean?
- Review every changed path listed beneath the branch header.
- Stop before creating the CI branch if those files belong to unfinished work.
Help me safely handle the existing repository changes.
That is the foundation checked. Your pipeline work now starts from a known repository state.
Isolate the pipeline work
A feature branch keeps the upcoming automation commits separate from main. You can review every CI change before it reaches the shared branch.
- Create the CI branch by running this command:
git switch -c feature/ci-container-pipeline
What does this command do?
This command creates the named feature branch from your current commit. It also switches the working tree to that branch.
Branch already exists?
- Pause before deleting or overwriting the existing branch.
- Check whether that branch contains earlier pipeline work.
Help me inspect the existing CI branch safely.
Before the final branch check, what branch name do you expect Git to show?
- Verify the active branch plus working-tree state by running this command:
git status --short --branch
What does this final status prove?
The branch header confirms where the next commit lands. An empty change list confirms that no project files changed during setup.
You should see feature/ci-container-pipeline as the active branch. You should see no pending file changes.
Before the final Docker check, do you expect both parts of the container engine to respond?
- Verify the Docker connection one final time by running this command:
docker version
What does this final check prove?
The repeated check confirms that the Docker connection remains available after you enter the repository and create the branch.
You should see both Client and Server sections again.
Your safety rail is in place. Next, you'll add the first workflow files without touching main or the live DentalFlow environment.
Get the First Green Container Build
Your safe feature branch is ready. The repository can now gain automation without changing the live DentalFlow environment.
A local Docker build proves the container works on your machine. A GitHub Actions run on a clean runner proves the repository contains everything needed to build it elsewhere.
In this step, get ready to:
- Keep local-only files out of the Docker build context.
- Create a workflow that builds the DentalFlow image and compiles its Python source.
- Push the workflow and inspect its first green validation run.
Exclude local-only files
A Docker build sends a folder called the build context to the builder. The backend/.dockerignore file keeps caches and machine-specific files out of that context.
- In the Explorer sidebar from earlier, expand the backend folder.
- Hover over the backend folder and click the New File icon.
- Enter .dockerignore in the filename field.
- Press Enter to create the file.
- Add the build-context exclusions by pasting this code into backend/.dockerignore:
__pycache__/
*.py[cod]
*.db
.env
.venv/
.pytest_cache/
What does this file exclude?
- The __pycache__/ directory and *.py[cod] pattern exclude generated Python cache files.
- The *.db pattern excludes local database files.
- The .env entry excludes local environment values.
- The .venv/ directory and .pytest_cache/ directory exclude local development artifacts.
- Save backend/.dockerignore by pressing Ctrl+S.
- Confirm Git can see the new file by running this command in the WSL terminal from earlier:
git status --short --branch
You should see backend/.dockerignore listed as a new untracked file beneath the current branch.
File missing from Git status?
- Confirm the file sits directly inside the backend folder.
- Check the filename starts with a dot and contains no extra file extension.
Ask for help checking the file location if it still does not appear. Help me find why backend/.dockerignore is missing from Git status.
✔️ Awesome, I've got everything!
Your Docker build context now excludes the local files that do not belong in a clean build.
ⓧ I'd like to double check the full code
Compare your backend/.dockerignore file with this complete version:
__pycache__/
*.py[cod]
*.db
.env
.venv/
.pytest_cache/
Each pattern should appear once in the same order.
Create the build-only workflow
A workflow file defines which events start automation. It also defines the jobs a clean GitHub-hosted runner performs.
- In the Explorer sidebar, click the New File icon beside the DentalFlow repository heading.
- Enter .github/workflows/ci.yml in the filename field.
- Press Enter to create the folders and workflow file.
- Define the workflow identity and trigger rules by pasting this first section into .github/workflows/ci.yml:
name: DentalFlow CI
on:
push:
pull_request:
branches:
- main
env:
IMAGE_NAME: dentalflow
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
How does the workflow start?
- The push trigger validates every pushed commit.
- The pull_request trigger validates pull requests targeting main.
- The IMAGE_NAME value gives later commands one consistent image identifier.
- The concurrency settings cancel an obsolete run when a newer commit arrives on the same ref.
- Save .github/workflows/ci.yml by pressing Ctrl+S.
- Confirm the saved section ends with cancel-in-progress: true.
Workflow section showing an editor warning?
YAML uses spaces to express nesting. Compare the indentation beneath pull_request and concurrency.
Ask for an indentation review if the warning remains. Help me check the indentation in my GitHub Actions workflow header.
- Add the read-only validation job directly below cancel-in-progress: true by pasting this section:
jobs:
validate:
name: Validate container
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
What does the validation job establish?
- The Validate container job runs on the ubuntu-24.04 runner.
- The contents: read permission gives validation read-only repository access.
- The checkout action retrieves the source code using an immutable commit reference for v6.
- Save .github/workflows/ci.yml.
- Confirm Check out repository appears beneath the job's steps section.
Checkout step outside the job?
Check that steps aligns beneath the other validate settings. Its checkout entry should sit one indentation level deeper.
Ask for help comparing the nesting if the editor still flags the job. Help me place the checkout step inside the validate job.
- Add the image build step directly below the pinned checkout line by pasting this section:
- name: Build commit-tagged image
run: |
docker build \
--label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
--tag "$IMAGE_NAME:$GITHUB_SHA" \
./backend
What does the build step prove?
- The build uses the existing backend/Dockerfile through the ./backend build context.
- The source label links the image metadata to the triggering repository.
- The $IMAGE_NAME:$GITHUB_SHA tag ties the local runner image to the triggering commit.
- Save .github/workflows/ci.yml.
- Confirm Build commit-tagged image aligns with Check out repository beneath steps.
Build step nested under checkout?
Both step names need the same indentation. Move the build entry left until its first dash aligns with the checkout entry's first dash.
Ask for help checking the workflow structure if alignment is unclear. Help me align the build and checkout steps in my workflow.
- Add the Python compilation gate directly below the build step by pasting this final section:
- name: Compile Python source
run: |
docker run --rm \
--entrypoint python \
"$IMAGE_NAME:$GITHUB_SHA" \
-m compileall -q /app/app
Why compile inside the image?
- The command starts the image built by the previous step.
- The --entrypoint python option runs Python instead of the application's normal startup command.
- The compilation check recursively scans the source under /app/app for Python syntax errors.
- The temporary container is removed when the check finishes.
- Save .github/workflows/ci.yml.
- Confirm Compile Python source is the final entry beneath steps.
Compilation step showing a YAML warning?
Confirm the command lines beneath run: | use consistent indentation. Check that each continued line retains its trailing backslash.
Ask for a line-by-line comparison if the warning persists. Help me compare the compile step with the complete workflow.
✔️ Awesome, I've got everything!
Your workflow now has triggers and a read-only validation job. It builds the image before compiling the Python source inside it.
ⓧ I'd like to double check the full code
Compare your complete .github/workflows/ci.yml file with this version:
name: DentalFlow CI
on:
push:
pull_request:
branches:
- main
env:
IMAGE_NAME: dentalflow
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Validate container
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Build commit-tagged image
run: |
docker build \
--label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
--tag "$IMAGE_NAME:$GITHUB_SHA" \
./backend
- name: Compile Python source
run: |
docker run --rm \
--entrypoint python \
"$IMAGE_NAME:$GITHUB_SHA" \
-m compileall -q /app/app
Use this full file only for comparison. Check the indentation and blank lines before continuing.
Push and inspect the first run
The workflow exists only in your local branch until Git sends it to GitHub. The push also gives GitHub Actions the event it needs to start validation.
- Review the pending files by running this command in the WSL terminal:
git status --short --branch
You should see .github/workflows/ci.yml and backend/.dockerignore listed beneath feature/ci-container-pipeline.
- Stage only the two CI files by running this command:
git add .github/workflows/ci.yml backend/.dockerignore
What did Git stage?
This command prepares the workflow and build-context exclusions for one focused commit. Existing application and infrastructure files remain outside the commit.
- Record the build-only workflow by running this command:
git commit -m "ci: build DentalFlow container"
Git records the two staged files under a commit message that describes this validation milestone.
Before you push, do you expect the clean runner to build the same DentalFlow image without relying on files from your local machine?
- Push the feature branch and set its upstream by running this command:
git push -u origin feature/ci-container-pipeline
Git uploads the new commit to the feature branch. The push starts the DentalFlow CI workflow.
- Return to the signed-in GitHub repository in your browser.
- Click the Actions tab to check the workflow triggered by your push.
- Select the newest DentalFlow CI run for feature/ci-container-pipeline.
- Open the Validate container job.
- Expand the Check out repository step to confirm the runner retrieved the repository.
- Expand the Build commit-tagged image step to confirm Docker built the image from ./backend.
- Expand the Compile Python source step to confirm the compilation gate completed.
That is your first remote win. The Validate container job is green because a clean runner checked out the repository and built the DentalFlow image successfully.
Validation job not green?
- Open the first red workflow step to identify whether checkout or the image build failed.
- Compare the failing step with the full workflow above before creating another commit.
- Check that backend/.dockerignore sits beside the existing backend/Dockerfile.
Share the failing step name without sharing credentials. Help me diagnose my first DentalFlow CI workflow failure.
Your repository can now build and compile DentalFlow on a clean runner. Next, you will add a runtime check that tests more than build success.
Make the Pipeline Prove the App Starts
That first green check matters: your GitHub Actions workflow now builds DentalFlow on a fresh runner.
A successful build proves that the Docker image can be assembled. It still leaves application startup untested.
This step adds a container smoke test that launches the image before calling its health endpoint. The result reveals whether DentalFlow is genuinely ready to serve requests.
In this step, get ready to:
- Add a container smoke test to the validation job.
- Add an always-running cleanup step for logs.
- Push the workflow to diagnose the remote startup result.
Add the container smoke test
A smoke test checks one critical path with the smallest useful request. Here, the workflow starts DentalFlow before asking its /health endpoint for a response.
- Switch back to .github/workflows/ci.yml in Visual Studio Code from earlier.
- Find the existing - name: Compile Python source block.
- Place your cursor after the complete compilation block.
- Start the smoke-test container by adding this workflow step:
- name: Start smoke-test container
run: |
docker run --detach \
--name dentalflow-ci \
--publish 127.0.0.1:8000:8000 \
"$IMAGE_NAME:$GITHUB_SHA"
What Does This Step Do?
- The --detach option starts the container in the background.
- The --name dentalflow-ci option gives the temporary container a predictable name.
- The --publish 127.0.0.1:8000:8000 option exposes the application only to the runner itself.
- The image tag uses GITHUB_SHA to test the image built from this exact commit.
- Save .github/workflows/ci.yml.
- Confirm the new block lines up with the existing compilation step under steps.
Does the New Step Look Misaligned?
Make sure - name: Start smoke-test container uses the same indentation as - name: Compile Python source.
Check that every command beneath run: | is indented farther than the run key.
Help me check the YAML indentation in my smoke-test container step.
Starting a container only proves that the runtime received a start command. The workflow needs an HTTP request to prove that DentalFlow stayed alive long enough to answer.
- Place your cursor directly below the Start smoke-test container block.
- Add the health endpoint check with this workflow step:
- name: Verify health endpoint
run: |
for attempt in {1..10}; do
response="$(curl --fail --silent http://127.0.0.1:8000/health || true)"
if [[ "$response" == '{"status":"ok"}' ]]; then
echo "DentalFlow health check passed"
exit 0
fi
sleep 2
done
echo "DentalFlow health check failed"
exit 1
How Does the Health Check Work?
- The loop gives the application up to ten attempts to start.
- The curl request calls http://127.0.0.1:8000/health without printing progress output.
- The success condition requires the exact response {"status":"ok"}.
- The final nonzero exit stops validation when no successful response arrives.
- Save .github/workflows/ci.yml.
- Check that Git sees your workflow edit by running this command in the Debian WSL terminal from earlier:
git status --short --branch
What Does This Check Confirm?
The command reports your current branch in a compact format. It also lists tracked files with changes that have not been committed.
You should still see feature/ci-container-pipeline as your branch. You should also see .github/workflows/ci.yml listed as modified.
Workflow File Missing from Git Status?
Confirm that you saved .github/workflows/ci.yml in Visual Studio Code. An unsaved file still shows a dot on its editor tab.
Help me find why my edited workflow is missing from Git status.
A failed health request needs diagnostic evidence. An unconditional cleanup step captures the application logs before removing the temporary container.
- Place your cursor directly below the Verify health endpoint block.
- Add the diagnostic cleanup with this workflow step:
- name: Show logs and remove smoke-test container
if: always()
run: |
docker logs dentalflow-ci || true
docker rm --force dentalflow-ci || true
Why Must Cleanup Always Run?
- The always() condition runs this step after either success or failure.
- The docker logs command preserves startup evidence for diagnosis.
- The docker rm --force command removes the temporary container from the runner.
- The || true suffix keeps cleanup from hiding the original result.
- Save .github/workflows/ci.yml.
- Compare your complete workflow with the reference below.
✔️ Awesome, I've got everything!
Your workflow now contains the three smoke-test steps in the correct order. Make sure .github/workflows/ci.yml is saved before continuing.
ⓧ I'd like to double check the full code
name: DentalFlow CI
on:
push:
pull_request:
branches:
- main
env:
IMAGE_NAME: dentalflow
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Validate container
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Build commit-tagged image
run: |
docker build \
--label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
--tag "$IMAGE_NAME:$GITHUB_SHA" \
./backend
- name: Compile Python source
run: |
docker run --rm \
--entrypoint python \
"$IMAGE_NAME:$GITHUB_SHA" \
-m compileall -q /app/app
- name: Start smoke-test container
run: |
docker run --detach \
--name dentalflow-ci \
--publish 127.0.0.1:8000:8000 \
"$IMAGE_NAME:$GITHUB_SHA"
- name: Verify health endpoint
run: |
for attempt in {1..10}; do
response="$(curl --fail --silent http://127.0.0.1:8000/health || true)"
if [[ "$response" == '{"status":"ok"}' ]]; then
echo "DentalFlow health check passed"
exit 0
fi
sleep 2
done
echo "DentalFlow health check failed"
exit 1
- name: Show logs and remove smoke-test container
if: always()
run: |
docker logs dentalflow-ci || true
docker rm --force dentalflow-ci || true
How Should the Workflow Compare?
- The existing build step remains unchanged.
- The existing compilation step remains unchanged.
- The three new steps start the container before probing its health.
- The final step captures logs before removing the container.
Does Your Workflow Differ?
Check the order of the three new steps. The container must start before the health request runs.
Confirm that if: always() belongs to the cleanup step.
Help me compare my smoke-test workflow with the expected YAML.
Commit the smoke test
A commit creates a reviewable checkpoint for the exact test that the remote runner executes. This checkpoint intentionally contains no runtime environment values.
- Stage the workflow change before recording the smoke-test checkpoint by running these commands:
git add .github/workflows/ci.yml
git commit -m "test: add container health smoke test"
What Do These Commands Record?
- The git add command stages only the workflow file.
- The git commit command records the smoke-test checkpoint with its supplied message.
You should see Git report a new commit containing the workflow change.
Does Git Say There Is Nothing to Commit?
Confirm that .github/workflows/ci.yml is saved. Check that your terminal is inside the local DentalFlow repository.
Help me diagnose why Git cannot commit my workflow change.
Push and inspect the designed failure
The remote runner now tests a path that the first green workflow never exercised. Its result distinguishes image construction from application readiness.
Before you push, predict whether the compiled image can stay running long enough to answer /health.
- Push the committed smoke test to the existing remote branch by running this command:
git push
What Does This Push Trigger?
- The command sends your committed workflow change to feature/ci-container-pipeline.
- The push trigger starts a new DentalFlow CI workflow run.
- Return to the DentalFlow repository in GitHub from earlier.
- Select the Actions tab in the repository navigation.
- Select the newest DentalFlow CI run.
- Select the Validate container job.
- Expand the Verify health endpoint step.
You will see the health check exhaust its retry loop before printing DentalFlow health check failed. The validation job turns red.
- Expand the Show logs and remove smoke-test container step.
- Read the startup logs captured before container removal.
The logs report missing settings named database_url and jwt_secret. The cleanup step still runs after the failed health request.
This Red Run Is Intentional
The image built successfully because construction never started the application. The smoke test exposes the missing runtime configuration during startup.
You now have useful application logs instead of a vague pipeline failure. That evidence identifies the next change precisely.
You have proved that a successful image build does not guarantee a successful application startup. Next, you will isolate the CI runtime before adding gated container delivery.
Fix Runtime Isolation and Add Container Delivery
The red GitHub Actions run proved that a valid container image can still hold an application that fails during startup.
This step isolates the SQS consumer from the health check. It also adds a gated delivery job for GitHub Container Registry while keeping the live AWS environment untouched.
In this step, get ready to:
- Start the SQS consumer only when an upload queue is configured.
- Supply disposable runtime configuration to the smoke-test container.
- Add a least-privilege publishing job for successful pushes to main.
Isolate the SQS consumer
DentalFlow's health route can run independently from the background SQS poller. The existing upload_queue_url setting becomes the boundary for starting that worker.
- Return to backend/app/main.py in VS Code.
- Select the module-level sqs_poll_loop import plus the entire current lifespan() function.
- Replace the selected code with this conditional lifespan function:
@asynccontextmanager
async def lifespan(app: FastAPI):
task = None
if settings.upload_queue_url:
from app.services.sqs_consumer import sqs_poll_loop
task = asyncio.create_task(sqs_poll_loop())
yield
if task is not None:
task.cancel()
What Does This Code Do?
- The task variable starts as None so shutdown can detect whether a worker exists.
- The if settings.upload_queue_url: condition starts the consumer only when a queue URL is configured.
- The local import keeps sqs_poll_loop outside the CI startup path.
- The shutdown block cancels the worker only when startup created one.
- Save backend/app/main.py.
- Confirm Git sees the application change by running this command in the Debian WSL terminal from earlier:
git status --short --branch
What Should You See?
The short status output should show feature/ci-container-pipeline as the current branch. It should also list backend/app/main.py as modified.
Main.py Missing From the Status Output?
Confirm the file tab does not show an unsaved-change dot. Check that you edited backend/app/main.py inside the DentalFlow repository.
Ask for help if the file still does not appear: Why does Git not show my backend/app/main.py change?
✔️ Awesome, I've got everything!
Your application startup now keeps the SQS consumer behind the existing queue configuration.
ⓧ I'd like to double check the full code
The full saved backend/app/main.py file should match this reference:
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.api.routes import auth, orders
from app.core.config import settings
@asynccontextmanager
async def lifespan(app: FastAPI):
task = None
if settings.upload_queue_url:
from app.services.sqs_consumer import sqs_poll_loop
task = asyncio.create_task(sqs_poll_loop())
yield
if task is not None:
task.cancel()
app = FastAPI(title="DentalFlow API", lifespan=lifespan)
app.include_router(auth.router)
app.include_router(orders.router)
@app.get("/health")
def health_check():
return {"status": "ok"}
What Should Match?
The routers and health route remain unchanged. The lifespan function now owns the conditional SQS import plus the optional background task.
Add CI-only configuration and delivery
The temporary smoke-test container needs disposable settings that satisfy application startup. Docker passes those values directly into that one container.
A separate publishing job creates a permission boundary. Validation keeps read-only repository access while publishing receives package-write access only after a successful push to main.
- Return to .github/workflows/ci.yml in VS Code.
- Find the existing Start smoke-test container step.
- Replace that step with this CI-configured version:
- name: Start smoke-test container
run: |
docker run --detach \
--name dentalflow-ci \
--publish 127.0.0.1:8000:8000 \
--env DATABASE_URL=postgresql+psycopg://ci:ci@localhost:5432/ci \
--env JWT_SECRET=ci-only-not-a-production-secret \
--env AWS_ACCESS_KEY_ID=testing \
--env AWS_SECRET_ACCESS_KEY=testing \
"$IMAGE_NAME:$GITHUB_SHA"
Why These Runtime Values?
- The DATABASE_URL value satisfies DentalFlow's required database setting during startup.
- The JWT_SECRET value satisfies token configuration inside the isolated test container.
- The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY values prevent the AWS client from searching for external credentials.
- All four values disappear when the cleanup step removes dentalflow-ci.
- Save .github/workflows/ci.yml.
- Confirm Git sees both edited files by running this command:
git status --short --branch
What Should You See Now?
The output should list both .github/workflows/ci.yml and backend/app/main.py as modified.
- Scroll to the end of .github/workflows/ci.yml.
- Add this publishing job beneath the validate job:
publish:
name: Publish container
needs: validate
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Log in to GitHub Container Registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build and push release image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
docker build \
--label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
--tag "$IMAGE_ID:$GITHUB_SHA" \
--tag "$IMAGE_ID:latest" \
./backend
docker push "$IMAGE_ID:$GITHUB_SHA"
docker push "$IMAGE_ID:latest"
How Is Publishing Gated?
- The needs: validate setting blocks publishing unless validation succeeds.
- The job condition limits delivery to push events on refs/heads/main.
- The packages: write permission exists only inside the publishing job.
- The short-lived GITHUB_TOKEN authenticates the workflow to the registry.
- The full GITHUB_SHA tag creates a traceable image while latest provides a convenient demonstration tag.
- Save .github/workflows/ci.yml.
✔️ Awesome, I've got everything!
Your workflow now validates every configured trigger. It publishes only after a successful push to main.
ⓧ I'd like to double check the full code
The full saved .github/workflows/ci.yml file should match this reference:
name: DentalFlow CI
on:
push:
pull_request:
branches:
- main
env:
IMAGE_NAME: dentalflow
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Validate container
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Build commit-tagged image
run: |
docker build \
--label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
--tag "$IMAGE_NAME:$GITHUB_SHA" \
./backend
- name: Compile Python source
run: |
docker run --rm \
--entrypoint python \
"$IMAGE_NAME:$GITHUB_SHA" \
-m compileall -q /app/app
- name: Start smoke-test container
run: |
docker run --detach \
--name dentalflow-ci \
--publish 127.0.0.1:8000:8000 \
--env DATABASE_URL=postgresql+psycopg://ci:ci@localhost:5432/ci \
--env JWT_SECRET=ci-only-not-a-production-secret \
--env AWS_ACCESS_KEY_ID=testing \
--env AWS_SECRET_ACCESS_KEY=testing \
"$IMAGE_NAME:$GITHUB_SHA"
- name: Verify health endpoint
run: |
for attempt in {1..10}; do
response="$(curl --fail --silent http://127.0.0.1:8000/health || true)"
if [[ "$response" == '{"status":"ok"}' ]]; then
echo "DentalFlow health check passed"
exit 0
fi
sleep 2
done
echo "DentalFlow health check failed"
exit 1
- name: Show logs and remove smoke-test container
if: always()
run: |
docker logs dentalflow-ci || true
docker rm --force dentalflow-ci || true
publish:
name: Publish container
needs: validate
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Log in to GitHub Container Registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build and push release image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
docker build \
--label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
--tag "$IMAGE_ID:$GITHUB_SHA" \
--tag "$IMAGE_ID:latest" \
./backend
docker push "$IMAGE_ID:$GITHUB_SHA"
docker push "$IMAGE_ID:latest"
What Should Match?
The validation job includes four CI-only environment values. The publishing job sits at the same indentation level as validate.
Only the publishing job has packages: write. Its branch condition points to refs/heads/main.
Seeing YAML Warnings?
Check that publish aligns with validate under jobs. Confirm each nested level uses consistent spaces.
Ask for help with the workflow structure: Why is my GitHub Actions publish job showing a YAML warning?
Commit and verify the branch run
The branch condition is the final safety gate for this run. Your feature branch should validate successfully while delivery remains skipped.
Before you push, do you expect the runtime configuration to turn validation green while the publishing job runs or stays skipped?
- Create the Git checkpoint by running these commands in the Debian WSL terminal:
git add backend/app/main.py .github/workflows/ci.yml
git commit -m "ci: publish validated images to GHCR"
git push
What Do These Commands Do?
- The first command stages the application startup change plus the final workflow.
- The second command records both files in one focused commit.
- The third command updates the existing remote feature branch. That push triggers DentalFlow CI.
Push Rejected or Workflow Missing?
Confirm the terminal still shows feature/ci-container-pipeline as the current branch. Check that the commit command completed before the push ran.
Ask for help with the checkpoint: Why did my DentalFlow CI push fail to start the workflow?
- Return to the DentalFlow repository page in GitHub.
- Select Actions in the repository navigation.
- Open the newest DentalFlow CI run.
- Open the Validate container job.
- Expand the Verify health endpoint step.
You'll see DentalFlow health check passed. That line appears only after the response matches {"status":"ok"}.
- Return to the workflow run overview.
- Locate the Publish container job.
You'll see a green Validate container job. You'll see Publish container marked as skipped because this push came from feature/ci-container-pipeline.
That is the recovery complete. DentalFlow now proves it can start safely before the workflow gains permission to publish an image.
Validation Still Red?
Open the cleanup step to confirm dentalflow-ci was removed. Read the container logs for any setting name that still blocks startup.
Ask for help with the failed run: Why is my DentalFlow smoke test still failing after adding CI-only settings?
Your feature branch now validates the running application while keeping package delivery gated to main. Next up, you'll document the design before reviewing and merging the pipeline.
Document, Review, and Publish the Pipeline
Your feature branch now has a green validation job. This step turns that result into a reviewed CI/CD pipeline with a published container image.
A working GitHub Actions workflow becomes useful evidence when another engineer can understand it. Documentation makes its triggers, quality gates, artifact, security boundary, and cleanup path reviewable.
In this step, get ready to:
- Document the complete pipeline design and its evidence.
- Review the four commits through a pull request before merging them into main.
- Publish a public image through GitHub Container Registry before pulling it into WSL.
Document the pipeline
A Markdown document gives reviewers a map of the automation they are about to inspect. It also records why the live DentalFlow environment stays outside this pipeline.
- In the Explorer sidebar from earlier, expand the docs folder.
- Expand the architecture folder.
- Hover over the architecture folder before clicking the New File icon.
- Type ci-cd-pipeline.md before pressing Enter.
- Add the document purpose and pipeline diagram to docs/architecture/ci-cd-pipeline.md by pasting this first section:
# DentalFlow CI/CD Pipeline
## Purpose
This pipeline validates every code change and publishes a container artifact after a successful merge to `main`. It does not deploy to Amazon ECS or change any live AWS resource.
## Pipeline flow
```mermaid
flowchart LR
A[Push or pull request] --> B[Build container]
B --> C[Compile Python source]
C --> D[Start isolated container]
D --> E[Call health endpoint]
E -->|Failure| F[Stop pipeline and show logs]
E -->|Success on feature branch or PR| G[Validation complete]
E -->|Success on main| H[Publish container]
H --> I[Commit SHA tag]
H --> J[latest tag]
```
What does this section capture?
- The purpose separates artifact delivery from live deployment.
- The Mermaid diagram traces each change through build, compilation, startup, health checking, and conditional publication.
- The two success branches show why feature work validates while only main publishes.
- Save docs/architecture/ci-cd-pipeline.md with Ctrl+S.
- Confirm the editor shows the Purpose heading above the complete pipeline diagram.
Diagram block look incomplete?
Check that the diagram begins with ```mermaid before ending with ```.
Help me compare my pipeline diagram with the required Mermaid block.
- Add the trigger matrix directly below the pipeline diagram by pasting:
## Trigger behavior
| Event | Validate container | Publish container |
|---|---:|---:|
| Push to a feature branch | Yes | No |
| Pull request targeting `main` | Yes | No |
| Push or merge to `main` | Yes | Yes |
What does the trigger matrix show?
The table makes the delivery boundary visible. Feature branches and pull requests validate changes while updates to main can publish an image.
- Save docs/architecture/ci-cd-pipeline.md.
- Confirm the trigger table contains three event rows with different publishing outcomes.
Trigger table difficult to read?
Keep each table row on its own line. Preserve every vertical bar so the three columns stay aligned.
Help me repair the trigger table in my pipeline document.
- Add the five validation gates directly below the trigger table by pasting:
## Quality gates
1. The Docker image must build from `backend/Dockerfile`.
2. Python must compile every source file under `/app/app`.
3. The container must start with CI-only configuration.
4. `GET /health` must return `{"status":"ok"}`.
5. The publish job cannot run unless validation succeeds.
Why list the quality gates?
Each numbered item names a condition that can stop the pipeline. Together they show that a successful build is only the first proof of application readiness.
- Save docs/architecture/ci-cd-pipeline.md.
- Confirm the quality-gate list runs from image construction through gated publication.
Quality-gate numbering broken?
Place each numbered requirement on a separate line. Keep the numbers in order from 1 through 5.
Help me repair the numbered quality-gate list.
- Document the isolated smoke-test configuration below the quality gates by pasting:
## Runtime isolation
DentalFlow normally starts an SQS consumer during application startup. The application now starts that consumer only when `UPLOAD_QUEUE_URL` has a value. The deployed ECS task supplies the queue URL, so its behavior is preserved. The CI smoke test leaves the queue URL empty, which prevents the test from contacting SQS.
The workflow supplies dummy values for `DATABASE_URL`, `JWT_SECRET`, `AWS_ACCESS_KEY_ID`, and `AWS_SECRET_ACCESS_KEY`. They exist only inside the temporary smoke-test container. They are not production credentials and are not stored as GitHub secrets.
What does runtime isolation prove?
The first paragraph explains how production keeps its SQS behavior. The second records that the smoke test uses temporary values without reaching live cloud services.
- Save docs/architecture/ci-cd-pipeline.md.
- Confirm the runtime section names both the conditional queue behavior and the four CI-only values.
Runtime boundary unclear?
Confirm the first paragraph discusses UPLOAD_QUEUE_URL. Confirm the second paragraph discusses the temporary smoke-test values.
Help me check whether my runtime-isolation section preserves the intended security boundary.
- Add the package path and image-tag strategy below the runtime section by pasting:
## Artifact naming
The package is published at `ghcr.io/keusuanl/dentalflow` with two tags:
- The full Git commit SHA provides an immutable link from source commit to image.
- `latest` points to the most recent successful `main` build.
No image is published from a pull request or feature-branch push.
Why use two image tags?
The full commit SHA provides a traceable artifact for a specific source revision. The latest tag provides a convenient demonstration target.
- Save docs/architecture/ci-cd-pipeline.md.
- Confirm the artifact section names ghcr.io/keusuanl/dentalflow before explaining both tag types.
Artifact path or tags missing?
Check that the package path uses lowercase letters. Keep the full commit SHA and latest as separate bullet points.
Help me correct the artifact-naming section.
- Add the final repository layout below the artifact section by pasting:
## Repository layout
```text
DentalFlow/
├── .github/
│ └── workflows/
│ └── ci.yml
├── backend/
│ ├── .dockerignore
│ ├── app/
│ │ └── main.py
│ └── Dockerfile
└── docs/
└── architecture/
└── ci-cd-pipeline.md
```
What does the layout communicate?
The tree connects each project artifact to its location. A reviewer can find the workflow, container rules, runtime change, and documentation without searching the whole repository.
- Save docs/architecture/ci-cd-pipeline.md.
- Confirm the repository tree begins with DentalFlow/ before ending with the architecture document.
Repository tree misaligned?
Keep the entire tree inside the fenced text block. Preserve the spaces before each nested path.
Help me fix the repository tree formatting.
- Record the workflow's permission and isolation choices below the repository layout by pasting:
## Security decisions
- The validation job has read-only repository access.
- Only the publish job receives `packages: write`.
- Publishing uses the temporary repository `GITHUB_TOKEN`, not a personal access token.
- The checkout action is pinned to a full commit SHA.
- The smoke-test port binds to `127.0.0.1` on the runner.
- The live AWS account, ECS service, database, and Terraform state are not accessed.
What makes the boundary visible?
The list shows where write access exists. It also records the immutable action pin, local smoke-test binding, and untouched live infrastructure.
- Save docs/architecture/ci-cd-pipeline.md.
- Confirm the security section contains six decisions before naming the untouched AWS resources.
Security choices look merged together?
Keep every security decision on its own bullet line. Check that packages: write appears only in the publication decision.
Help me compare my six security decisions with the final pipeline design.
- Finish the document with the failure lesson, evidence checklist, and cleanup choices by pasting:
## Failure lesson
The first smoke-test run intentionally starts the image without required runtime settings. The image builds, but the application exits before the health check. This demonstrates that build success and runtime readiness are different quality gates.
## Evidence checklist
- A green feature-branch `Validate container` run
- A red run showing the designed runtime-configuration failure
- A green pull-request check
- A green `main` run containing both jobs
- A package page showing `latest` and a commit-SHA tag
- A successful pull of `ghcr.io/keusuanl/dentalflow:latest`
## Cleanup
The workflow can be paused from the repository's Actions page with `Disable workflow`. The package can be removed from `Package settings` with `Delete this package`. Neither action changes the live AWS environment.
How does the document finish?
- The failure lesson records why container startup needs its own quality gate.
- The evidence checklist turns workflow runs and package tags into demonstration material.
- The cleanup section explains how to pause automation or remove the package without touching AWS.
- Save docs/architecture/ci-cd-pipeline.md.
- Confirm the file ends with the cleanup paragraph about the live AWS environment.
Final sections missing?
Check for the Failure lesson, Evidence checklist, and Cleanup headings near the bottom of the file.
Help me find any missing final section in my pipeline document.
✔️ Awesome, I've got everything!
Your architecture document now explains the pipeline from trigger to cleanup. Double check that docs/architecture/ci-cd-pipeline.md is saved before continuing.
ⓧ I'd like to double check the full code
Compare your complete docs/architecture/ci-cd-pipeline.md file with this reference:
# DentalFlow CI/CD Pipeline
## Purpose
This pipeline validates every code change and publishes a container artifact after a successful merge to `main`. It does not deploy to Amazon ECS or change any live AWS resource.
## Pipeline flow
```mermaid
flowchart LR
A[Push or pull request] --> B[Build container]
B --> C[Compile Python source]
C --> D[Start isolated container]
D --> E[Call health endpoint]
E -->|Failure| F[Stop pipeline and show logs]
E -->|Success on feature branch or PR| G[Validation complete]
E -->|Success on main| H[Publish container]
H --> I[Commit SHA tag]
H --> J[latest tag]
```
## Trigger behavior
| Event | Validate container | Publish container |
|---|---:|---:|
| Push to a feature branch | Yes | No |
| Pull request targeting `main` | Yes | No |
| Push or merge to `main` | Yes | Yes |
## Quality gates
1. The Docker image must build from `backend/Dockerfile`.
2. Python must compile every source file under `/app/app`.
3. The container must start with CI-only configuration.
4. `GET /health` must return `{"status":"ok"}`.
5. The publish job cannot run unless validation succeeds.
## Runtime isolation
DentalFlow normally starts an SQS consumer during application startup. The application now starts that consumer only when `UPLOAD_QUEUE_URL` has a value. The deployed ECS task supplies the queue URL, so its behavior is preserved. The CI smoke test leaves the queue URL empty, which prevents the test from contacting SQS.
The workflow supplies dummy values for `DATABASE_URL`, `JWT_SECRET`, `AWS_ACCESS_KEY_ID`, and `AWS_SECRET_ACCESS_KEY`. They exist only inside the temporary smoke-test container. They are not production credentials and are not stored as GitHub secrets.
## Artifact naming
The package is published at `ghcr.io/keusuanl/dentalflow` with two tags:
- The full Git commit SHA provides an immutable link from source commit to image.
- `latest` points to the most recent successful `main` build.
No image is published from a pull request or feature-branch push.
## Repository layout
```text
DentalFlow/
├── .github/
│ └── workflows/
│ └── ci.yml
├── backend/
│ ├── .dockerignore
│ ├── app/
│ │ └── main.py
│ └── Dockerfile
└── docs/
└── architecture/
└── ci-cd-pipeline.md
```
## Security decisions
- The validation job has read-only repository access.
- Only the publish job receives `packages: write`.
- Publishing uses the temporary repository `GITHUB_TOKEN`, not a personal access token.
- The checkout action is pinned to a full commit SHA.
- The smoke-test port binds to `127.0.0.1` on the runner.
- The live AWS account, ECS service, database, and Terraform state are not accessed.
## Failure lesson
The first smoke-test run intentionally starts the image without required runtime settings. The image builds, but the application exits before the health check. This demonstrates that build success and runtime readiness are different quality gates.
## Evidence checklist
- A green feature-branch `Validate container` run
- A red run showing the designed runtime-configuration failure
- A green pull-request check
- A green `main` run containing both jobs
- A package page showing `latest` and a commit-SHA tag
- A successful pull of `ghcr.io/keusuanl/dentalflow:latest`
## Cleanup
The workflow can be paused from the repository's Actions page with `Disable workflow`. The package can be removed from `Package settings` with `Delete this package`. Neither action changes the live AWS environment.
How to use this reference
Compare the headings, tables, fenced blocks, lists, and paragraphs in order. The final line should state that cleanup leaves the live AWS environment unchanged.
Commit and review the pull request
The document now gives reviewers a map for every changed file. Your next checkpoint records it in Git before the pull request tests the complete branch.
- Return to the terminal panel from earlier.
- Stage the architecture document by running:
git add docs/architecture/ci-cd-pipeline.md
What does this command do?
The command adds the completed architecture document to Git's index. The next commit can now include that file.
The prompt returns without an error when the document is staged successfully.
- Commit the staged documentation by running:
git commit -m "docs: explain DentalFlow CI CD pipeline"
What does this command record?
The command creates the fourth project commit with a message dedicated to the architecture documentation. This keeps the review history easy to follow.
You should see a commit summary that lists the new documentation file.
Before you push, which job do you expect to run on this feature branch? The result comes next.
- Push the documentation commit to the existing feature branch by running:
git push
What does this push trigger?
The push updates feature/ci-container-pipeline on GitHub. The workflow validates the branch while keeping publication restricted to main.
You should see the terminal confirm that the remote feature branch received the new commit.
Push rejected?
Confirm the terminal still points to the cloned DentalFlow repository. Check that the branch name is feature/ci-container-pipeline.
Help me diagnose why my documentation commit will not push.
- Return to the DentalFlow repository page where you are signed in.
- Click Compare & pull request for feature/ci-container-pipeline.
- Confirm the base branch is main.
- Confirm the compare branch is feature/ci-container-pipeline.
- Click Create Pull Request.
The pull request now gathers the complete CI/CD change into one review surface. Its check starts validating the same branch state you just pushed.
- Review the four commits listed in the pull request.
- Review the changed files for the workflow, Docker ignore rules, conditional SQS startup, and architecture document.
- Confirm Validate container finishes with a green result.
The green check proves the reviewed branch still builds, compiles, starts, and answers its health request. The publish job remains skipped because the pull request has not changed main.
Before you merge, do you expect Publish container to stay skipped after main receives the commit? The next workflow run reveals the answer.
- Click Merge pull request after the green check completes.
- Confirm the merge in the dialog GitHub displays.
That review is complete. Your four commits now form the documented pipeline on main.
- Select the repository's Actions tab.
- Open the new DentalFlow CI run for main.
- Confirm Validate container finishes green.
- Confirm Publish container finishes green.
Both jobs are green because main passed every gate before receiving package-write permission. The delivery stage has now published the validated image.
Make the package public and pull it
A package published under a personal account initially uses private visibility. Changing it to public lets anyone pull the DentalFlow image without GitHub credentials.
Public visibility is permanent
This visibility change cannot be reversed to private. The package contains the project image intended for public demonstration, so this permanent setting matches the pipeline goal.
- Return to the DentalFlow repository page.
- Open the new dentalflow package.
- Select Package settings.
- Choose Change visibility.
- Select Public.
- Complete the confirmation GitHub displays.
The package page now identifies ghcr.io/keusuanl/dentalflow as public. That visibility makes the artifact available for an independent pull test.
- Return to the dentalflow package page.
- Confirm the package lists a latest tag.
- Confirm another tag matches the full merge commit SHA shown in the main workflow run.
A public Container registry package can be downloaded without authentication. Docker now gives you an independent check that the published image is reachable from Debian on WSL.
Before you pull the image, do you expect Docker to request GitHub credentials for this public package? The terminal result gives you the answer.
- Switch back to the Debian WSL terminal from earlier.
- Verify the public artifact by running:
docker pull ghcr.io/keusuanl/dentalflow:latest
What does this command prove?
The command asks Docker to download the image identified by the public package path and latest tag. A successful pull proves the registry artifact is available outside the workflow runner.
You should see Docker download the image layers or confirm that the local copy is current. The output identifies ghcr.io/keusuanl/dentalflow:latest as the pulled image.
Image pull denied or missing?
Return to Package settings before confirming the package visibility is Public. Check that the package path uses lowercase keusuanl/dentalflow.
Help me diagnose why the published DentalFlow image will not pull.
You have closed the complete delivery loop from reviewed source to a pullable container image. AWS, ECS, RDS, VPC resources, and Terraform state remain untouched.
Secret mission
Protect `main` with the CI Check
A green CI result is advisory until repository policy enforces it. Protect `main` so every future change needs a pull request with a successful `Validate container` check.
Clean Up Your Resources
Clean Up Your Resources
This public repository currently has no ongoing cost for standard GitHub-hosted runners or GitHub Container Registry image storage. Choose whether to keep the pipeline active, pause new runs, or remove its resources.
Resources you used:
- One GitHub Actions workflow stored at .github/workflows/ci.yml.
- One public GitHub Container Registry package at ghcr.io/keusuanl/dentalflow with latest plus commit-SHA versions.
- One CI/CD architecture document stored at docs/architecture/ci-cd-pipeline.md.
- One Docker build-context filter stored at backend/.dockerignore.
- One classic branch protection rule for main that requires Validate container.
- One open policy-proof pull request from the remote feature/ci-policy-proof branch.
Your live AWS infrastructure stayed untouched. Your Terraform state needs no cleanup.
Keep everything running
No action is needed. Choose this if you want future commits to keep producing validation evidence and container images.
Feature-branch pushes continue to run Validate container. Successful pushes to main continue to update the package tags.
The branch protection rule continues to block changes that do not pass the required check.
Pause - I'll come back to this later
Shut down future workflow runs while keeping the published package and repository files available.
- Return to the DentalFlow repository on GitHub.
- Select Actions.
- Select DentalFlow CI.
- Open the workflow menu.
- Select Disable workflow.
- Leave new pull requests unmerged while the workflow is disabled.
The existing package remains publicly available. Its latest and commit-SHA versions remain unchanged.
Delete - I don't want to use this again
Full cleanup needs a specific order. The main protection rule depends on Validate container, so deleting the workflow first can leave the cleanup change without its required check.
- Return to the DentalFlow repository on GitHub.
- Select Settings.
- Select Branches.
- Locate the classic branch protection rule for main.
- Remove the classic branch protection rule for main.
The required check can no longer block the commit that removes its workflow.
- Return to the open pull request from feature/ci-policy-proof.
- Close the policy-proof pull request.
- Open the repository's branch list.
- Delete the remote feature/ci-policy-proof branch.
The remaining repository resources are files tracked by Git. Deleting them through Git records the cleanup in the repository history.
- Return to the local DentalFlow clone in Visual Studio Code.
- Switch to main with Git.
- Delete .github/workflows/ci.yml from the local clone.
- Delete backend/.dockerignore from the local clone.
- Delete docs/architecture/ci-cd-pipeline.md from the local clone.
- Commit the three deletions with Git.
- Push main to GitHub.
Deleting the public package can feel final. GitHub keeps a deleted package restorable for 30 days if its namespace remains available.
- Return to the dentalflow package on GitHub.
- Select Package settings.
- Scroll to Danger Zone.
- Select Delete this package.
- Complete the confirmation shown by GitHub.
The workflow, package, documentation, build-context filter, policy-proof branch, and repository rule are now removed. The remaining DentalFlow application and live AWS infrastructure stay in place.
Nice Work!
Nice Work!
You did it! DentalFlow now has a secure CI/CD chain where GitHub Actions validates its Docker container before publishing it to GitHub Container Registry.
You've learned how to:
- Built a repeatable container validation pipeline. It builds the image before compiling Python source. It finishes by testing /health.
- Used container logs to diagnose the designed runtime-configuration failure. Added CI-only runtime configuration for the smoke test. Left the live AWS environment unchanged.
- Created a gated delivery stage that publishes images only after validation succeeds. Added an immutable commit-SHA tag. Added a convenient latest tag. Kept package-writing permission inside the publishing job.
- Completed the Secret Mission by protecting main. Future changes require a pull request. The Validate container status check must pass before merge.
Ready to quiz yourself?