Capstone 3: ship TaskFlow
Containerize TaskFlow, run it with Docker Compose, automate CI with GitHub Actions, and optionally deploy it to the cloud.
30 min read
You will learn
- Containerize the API with a multi-stage Dockerfile
- Run the whole system with Docker Compose
- Build and test every change with GitHub Actions
- Deploy (optionally) and reflect on your design
From "works on my machine" to shipped
TaskFlow works locally. Now make it reproducible, automatically tested, and deployable.
Milestone 1 — Containerize
Add a multi-stage Dockerfile at the solution root:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY src/TaskFlow.Api/TaskFlow.Api.csproj src/TaskFlow.Api/
RUN dotnet restore src/TaskFlow.Api/TaskFlow.Api.csproj
COPY . .
RUN dotnet publish src/TaskFlow.Api -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
USER app
EXPOSE 8080
ENTRYPOINT ["dotnet", "TaskFlow.Api.dll"]…and a .dockerignore with bin/, obj/, .git/, tests/. Build and run it against your local database to check it starts.
Covered in: Docker for .NET.
Milestone 2 — Docker Compose
Describe the whole system in docker-compose.yml: the API, PostgreSQL with a volume, and (optionally) a fake SMTP server like MailHog/Mailpit so you can see the assignment emails in a web UI.
services:
api:
build: .
ports: ["8080:8080"]
environment:
ConnectionStrings__TaskFlow: "Host=db;Database=taskflow;Username=postgres;Password=dev-only"
ASPNETCORE_ENVIRONMENT: Development
depends_on: [db]
db:
image: postgres:16
environment: { POSTGRES_PASSWORD: dev-only, POSTGRES_DB: taskflow }
volumes: ["db-data:/var/lib/postgresql/data"]
mail:
image: axllent/mailpit
ports: ["8025:8025"]
volumes:
db-data:Decide how migrations run: at startup in development only (db.Database.MigrateAsync()), and as a separate pipeline step for real environments.
Goal: a new developer runs docker compose up and has a working system.
Milestone 3 — Continuous integration
Push the repository to GitHub and add .github/workflows/ci.yml:
- Trigger on pushes and pull requests.
- Restore, build (
-c Release), run all tests (Testcontainers works on GitHub's Ubuntu runners). - Check for vulnerable packages.
- Build the Docker image to make sure it still builds.
Then protect main: require the CI check to pass before merging.
Covered in: CI/CD with GitHub Actions.
Milestone 4 — Deploy (optional)
Pick a target that fits your budget — many offer free tiers or credits:
- Azure Container Apps + Azure Database for PostgreSQL, or
- Azure App Service (deploy the published output), or
- Any container platform (Fly.io, Railway, Render, Google Cloud Run).
Whichever you choose:
- Configuration and secrets via the platform's settings / Key Vault — never in the image.
/healthconfigured as the health check.- Logs (and ideally OpenTelemetry traces) visible somewhere you can search.
- A budget alert, and remember to delete the resources when you're done experimenting.
Extend the workflow with a deploy job on main.
Covered in: Deploying to the cloud · Running in production.
Milestone 5 — README and retrospective
Write a README.md a stranger could follow: what TaskFlow is, how to run it (docker compose up), how to run the tests, the API overview (link to the OpenAPI document) and the architecture diagram.
Then revisit docs/design.md and add a retrospective:
- What changed between the design and the implementation, and why?
- Which decision would you make differently?
- What breaks first at 10× the users? (Hint: think about "my tasks" queries, notification volume, and a single database.) How would you fix it — caching, read replicas, a real message broker?
- What would you add next: real-time updates with SignalR? File attachments in object storage? Search?
Show it off
A deployed, tested, containerized API with a design document and a clear README is an excellent portfolio project. You've used the skills a professional .NET backend developer uses every day.
Checklist
- Multi-stage Dockerfile builds and runs the API
-
docker compose upstarts the API, database (and mail catcher) - GitHub Actions builds and tests every push and pull request;
mainis protected - (Optional) Deployed with health checks, logs and secrets handled properly
- README and design retrospective written
Congratulations 🎉
You've gone from your first Console.WriteLine to designing, building and shipping a production-style backend. Keep building — and revisit any lesson whenever you need a refresher.
Finished reading? Mark the lesson as complete to track your progress.