Manikandan — Manikandan
Microservices

Day 41: Service Template

ManikandanManikandan
17 min read·Updated Sep 19, 2022

A Service Template is a ready-made skeleton repository that every new microservice starts from.

Intro

A Service Template is a ready-made skeleton repository that every new microservice starts from. It already contains the folder layout, Dockerfile, CI/CD pipeline, lint and analyzer rules, test projects, health endpoints and a baseline README, so a team can go from “new service” to “first deployment” in hours instead of weeks. It answers the question “how do we build a service here?” once, in code, instead of in a wiki nobody reads.

Why we need this

In an insurance claims system you might end up with claims-intake, policy-lookup, fraud-scoring, payments and notifications, each owned by a different team. Without a template, each team makes its own choices about repo layout, branching, build scripts, Dockerfile base images, code analysis, test structure and pipeline stages.

Business reasons:

  • Time to first deploy. A new service should reach a working “hello world” in a non-production environment on day one, not after two weeks of pipeline plumbing.
  • Consistent security posture. Auditors ask, “Do all services scan dependencies and run as non-root?” With a template the answer is yes by construction.
  • Lower onboarding cost. A developer moving between teams recognises the layout immediately.

Technical reasons:

  • Build, test and deploy steps become predictable, so platform and SRE teams can support them.
  • Quality gates (analyzers, code coverage, vulnerability scans) are wired in from the first commit, when it is cheapest to have them.
  • Upgrades (a new .NET LTS, a new base image, a new pipeline step) can be rolled out by updating one template and a migration guide.

What problem it solves

Problem (from the topic list): inconsistent builds, repo layouts and lint rules across teams.

What goes wrong without it:

  • claims-intake uses src/ and tests/, payments uses Source/ and UnitTests/, and notifications puts everything in one project. Shared CI scripts cannot assume anything.
  • One team’s Dockerfile runs as root on a 1 GB SDK image; another’s is a multi-stage build on a runtime-only image running as non-root. A vulnerability scan flags one and not the other.
  • Warnings are errors in one repo and ignored in another. Code style arguments show up in every pull request.
  • A critical fix (for example, adding a dependency vulnerability scan) must be hand-copied into 30 pipelines, and 6 of them never get it.
  • New services take weeks because each team re-invents the pipeline, and those weeks are billed as “platform work” nobody planned for.

A Service Template moves those decisions into one reviewed, versioned place and lets teams start from a known-good baseline.

When it is needed (and when it is NOT)

Use a template when:

  • You have, or will soon have, more than roughly 5 services built by more than one team.
  • New services are created regularly (a few per quarter or more).
  • Your organisation has real standards (security, compliance, observability) that should not depend on memory.
  • You have a platform or architecture group that can own and maintain the template.

Do not use, or be careful, when:

  • You have one or two services. A README and a copy of a good repo is enough; a template engine is overhead.
  • Services are deliberately heterogeneous (for example a Python ML service and a .NET API); one template will not fit, and you need one per stack.
  • Nobody owns the template. An unmaintained template is worse than none, because it spreads outdated practices at scale.
  • You are tempted to put runtime behaviour into it. Runtime behaviour shared across services belongs in the Microservice Chassis (Day 40, a library), not in copied code.

Template vs chassis in one line: the template is copied once at creation and then owned by the service team; the chassis is referenced continuously as a versioned package.

How to identify the problem (key signals)

  1. Pull requests about style. Review comments like “we use file-scoped namespaces here” or “please sort usings” appear in many repos, and different repos have different answers.
  2. Time-to-first-deploy above a week for a trivial new service, mostly spent on pipeline and Dockerfile work.
  3. Pipeline drift. Comparing azure-pipelines.yml or .github/workflows/*.yml across repos shows dozens of variants of the same steps.
  4. Security scan findings that differ by repo for the same reason (root containers, old base images, missing SAST).
  5. Engineers ask “where do I put X?” in chat every time they open an unfamiliar repo.
  6. Audit or compliance findings such as “3 of 12 services have no dependency scanning”.
  7. Rollout of a platform change takes months because every repo needs a bespoke fix.

Flow Diagram

A versioned template creates a new service that builds, tests and deploys on day one.

flowchart LR
T["co-service template v3"] -- "dotnet new co-service" --> NEW["Company.Payments repo"]
NEW --> P["Reusable pipeline workflow"]
P --> B["Build - warnings as errors"]
B --> TS["Tests + dependency scan"]
TS --> IMG["Image to ACR"]
IMG --> DEP["Dev Container Apps"]
NEW -. ".template-version" .-> RPT["Drift report"]

Level 1: Beginner

Analogy: A house-building company does not design a new floor plan for each house. It has a standard plan with wiring, plumbing and fire exits already placed. A buyer picks the finishes. A Service Template is that standard plan for a microservice.

Minimal working example. .NET ships a template engine. You can create your own template with dotnet new. Layout of a tiny template:

claims-service-template/
├── .template.config/
│ └── template.json
├── src/
│ └── Company.Claims/
│ ├── Company.Claims.csproj
│ └── Program.cs
├── tests/
│ └── Company.Claims.Tests/
│ ├── Company.Claims.Tests.csproj
│ └── HealthTests.cs
├── Dockerfile
├── .editorconfig
└── README.md

.template.config/template.json:

{
"$schema": "http://json.schemastore.org/template",
"author": "Platform Team",
"classifications": ["Web", "API", "Microservice"],
"identity": "Company.Templates.ClaimsService",
"name": "Company Microservice",
"shortName": "co-service",
"sourceName": "Company.Claims",
"preferNameDirectory": true,
"tags": { "language": "C#", "type": "project" }
}

sourceName is the token replaced by the name the developer passes with -n. Install and use it:

Terminal window
dotnet new install ./claims-service-template
dotnet new co-service -n Company.Payments

The result is a new folder Company.Payments with all namespaces, project names and file names replaced. A developer now has a compiling service, a test project and a Dockerfile in one command.

Level 2: Intermediate

In a real .NET 10 (LTS) + Angular + database stack, a template must be more than folders. It should include the parts every service needs from day one.

What a good backend template contains:

  • Directory.Build.props for shared compiler settings, analyzers and versions.
  • Directory.Packages.props for central package management.
  • .editorconfig for formatting and analyzer severity.
  • A minimal API project with health checks, OpenTelemetry wiring and structured logging (provided by the chassis package from Day 40).
  • A test project using xUnit and WebApplicationFactory.
  • A multi-stage Dockerfile running as a non-root user.
  • CI pipeline that restores, builds with warnings as errors, tests, scans and builds an image.
  • An EF Core migrations folder or a database-project stub, depending on team standard.

Directory.Build.props (shared by every project in the repo):

<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
</Project>

Program.cs in the template (includes the health endpoints an orchestrator needs):

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks()
.AddCheck("self", () => Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Healthy());
var app = builder.Build();
app.MapHealthChecks("/health/live");
app.MapHealthChecks("/health/ready");
app.MapGet("/", () => "Company.Claims is running");
app.Run();
public partial class Program { } // needed so tests can use WebApplicationFactory<Program>

A first test that ships with the template, so the pipeline has something real to run:

using System.Net;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
public class HealthTests(WebApplicationFactory<Program> factory)
: IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task Live_endpoint_returns_200()
{
var client = factory.CreateClient();
var response = await client.GetAsync("/health/live");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}

Multi-stage Dockerfile with a non-root user (the app user exists in the official .NET 8+ images):

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish src/Company.Claims/Company.Claims.csproj -c Release -o /out
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /out .
USER app
EXPOSE 8080
ENTRYPOINT ["dotnet", "Company.Claims.dll"]

Angular side. The Angular client for a domain (for example the claims portal) needs the same treatment. A frontend template usually starts from ng new with your defaults committed: strict TypeScript, ESLint, Prettier config, a standard folder structure (core/, shared/, features/), and a CI pipeline running ng lint, ng test and ng build. Use Angular schematics or a repo template for this; avoid a hand-written generator unless you need it.

Database. Put the migration tooling choice into the template (EF Core migrations, or DbUp, or a SQL project) so every service handles schema change the same way. For the running example, each service owns its own database (Day 5), so the template includes a docker-compose.yml for local development that starts a SQL Server or PostgreSQL container.

Level 3: Advanced

Two delivery mechanisms, and their trade-offs:

MechanismHow it worksStrengthWeakness
dotnet new custom templatePacked as a NuGet package or installed from a folderNative to .NET, parameters and conditional content supportedOnly .NET; output is a one-time copy
GitHub/Azure DevOps “template repository”Click “Use this template”Zero tooling, works for any stackNo parameters, manual renames
Cookiecutter / Copier / YeomanGeneric scaffolding toolMulti-stack, Copier supports later updatesAnother tool to learn and maintain
Backstage Software TemplatesPortal form runs scaffolder, creates repo and registers it in catalogSelf-service, creates repo, pipeline and catalog entry togetherRequires running a Backstage instance

Keeping the copies from rotting (template drift). The core weakness of any template is that services diverge after creation. Mitigate it:

  • Keep the logic that must change over time out of the copied files. Put shared pipeline steps in reusable pipeline templates (Azure Pipelines extends templates, GitHub reusable workflows) and shared code in the chassis package. The service’s own pipeline file then only says uses: company/platform/.github/workflows/dotnet-service.yml@v3.
  • Version the template (semantic version in a file) and record the template version inside each generated repo (for example .template-version). Then a script can list which services are behind.
  • Use Copier or a bot (Renovate for dependencies, scheduled pull requests for template changes) to propose updates.

Security considerations:

  • Never put real secrets, connection strings or tenant IDs in a template. Use placeholders and reference Key Vault (Day 39).
  • Enable dependency and secret scanning in the template’s pipeline by default.
  • Run containers as non-root, and use a pinned base image tag or digest.
  • Use federated credentials (OIDC) for the pipeline to authenticate to Azure, so the template does not carry a service principal secret.

Failure modes and common mistakes:

  • Template too big. Including every possible feature (Redis, Service Bus, Cosmos DB) means every service starts with unused code. Include only what all services need; offer optional modules through template parameters.
  • Template as a gate. If the template is mandatory and slow to change, teams route around it. Treat it as a paved road: easiest path, not the only path.
  • No owner. Nobody updates it, and it pins .NET 8 after .NET 10 is the LTS. .NET 8 and .NET 9 reach end of support on 10 November 2026, so a template that still targets them starts new services on runtimes that are about to lose security patches.
  • Copy-paste of the template’s own bugs into 40 repos. Add a test that generates a service from the template and builds it in CI (see exercise).
  • Confusing template and chassis. Behaviour that should be patched centrally (auth handling, logging enrichment) inside template code cannot be patched centrally.

Level 4: Expert and Architect view

Design trade-offs and alternatives:

OptionConsistencyAutonomyUpdate pathBest when
Service Template (copy once)Medium at creation, decaysHighManual or bot-driven PRsMany services, teams want ownership
Microservice Chassis (shared library)High and continuousMediumNuGet version bumpRuntime cross-cutting concerns
Reusable pipeline templatesHigh for CI/CDHigh for codeCentral change, pinned by versionPipeline logic shared across repos
Monorepo with shared buildVery highLowSingle commitSmall org, tight coupling accepted
Wiki guidelines onlyLowVery highNoneTwo or three services
Platform portal (Backstage) plus templatesHigh at creation, catalog keeps trackHighPortal plus botsLarger orgs with a platform team

Patterns it combines with:

  • Microservice Chassis (Day 40). The template references the chassis package; the chassis carries runtime behaviour, the template carries structure.
  • Service per Team (Day 4). Each team can create services without asking a central group.
  • Externalized Configuration (Day 39). The template wires config and secret loading.
  • Health Check API (Day 36). Health endpoints ship in the template.
  • Service per Container (Day 42) and Service Deployment Platform (Day 46). The template’s Dockerfile and deployment manifests fit the target platform.
  • Consumer-Driven Contract Test (Day 51) and Service Component Test (Day 50). The template includes the test project scaffolding for both.

ADR-style justification:

ADR-041: Adopt a versioned Service Template for new .NET microservices

Status: Proposed

Context: The claims platform has 14 services across 5 teams. Repo layouts, Dockerfiles and pipelines differ. Vulnerability scanning is missing in 4 repos. New services take about 2 weeks to reach a first deployment.

Decision: The platform team will publish a dotnet new template (co-service) plus reusable pipeline templates. New services must start from it. Runtime cross-cutting behaviour stays in the chassis NuGet package. The template version is recorded in each repo.

Consequences: Positive: consistent baseline, faster start, security controls by default. Negative: platform team owns maintenance; existing services need a one-time alignment; the template can drift after creation. Mitigation: reusable pipelines carry the fast-changing parts, and a monthly report lists services behind the current template version.

Alternatives considered: wiki guidelines (no enforcement), monorepo (blocks team autonomy), chassis only (cannot standardise repo layout or Dockerfile).

Azure implementation

A Service Template is a development-time asset, not an Azure runtime service, but Azure supplies the pieces the template wires in.

Azure services and tools that support it:

  • Azure Developer CLI (azd). Supports templates that combine app code, infrastructure as code (Bicep or Terraform) and pipeline definitions. azd init --template <name> scaffolds a project, azd up provisions and deploys. Azure documents azd templates for Azure Container Apps, which is a good starting point for the internal template.
  • Azure Container Registry (ACR). Holds the images that the template’s pipeline builds. Tiers are Basic, Standard and Premium; Premium adds features such as geo-replication and private link. Choose based on throughput, storage and networking needs, and check the current pricing page for figures.
  • Azure Container Apps or AKS. The deployment target the template’s manifests or Bicep files describe. Container Apps uses a consumption-based model with a monthly free grant on the Consumption plan; confirm current amounts on the pricing page before quoting them.
  • Azure Pipelines or GitHub Actions. Runs the reusable pipeline templates. Use OIDC / workload identity federation to sign in to Azure without stored secrets.
  • Azure Key Vault. Holds secrets referenced by the deployed service (Day 39). The template’s Bicep creates the access policy or RBAC assignment.
  • Azure Monitor / Application Insights. The template configures OpenTelemetry export (through the chassis) so every service reports telemetry from its first deployment.
  • Azure Artifacts or NuGet feed. Host the template package and the chassis package.
  • Microsoft Defender for Cloud / GitHub Advanced Security. Dependency, secret and container scanning wired into the pipeline.

How to configure (outline):

  1. Publish the template package: dotnet pack the template project and push to your Azure Artifacts feed.
  2. Add a Bicep folder (infra/) to the template with modules for Container App, ACR pull permission (managed identity plus AcrPull role), Key Vault reference and Application Insights connection.
  3. In the pipeline, log in using federated credentials, build the image with az acr build or docker build, then deploy using azd deploy or az containerapp update.
  4. Add branch policies and required checks (build, test, scan) on the template’s default branch and on generated repos.

Pricing and tier considerations:

  • The template itself costs nothing to host; a Git repo and a package feed are enough.
  • Costs appear in what services created from it consume: ACR tier, Container Apps or AKS compute, Application Insights ingestion, Key Vault operations.
  • Default the template’s non-production environments to the smallest workable sizes and scale-to-zero on Container Apps, so a forgotten test service does not run up a bill.
  • Verify SKU names and prices against the current Azure pricing pages when writing the ADR; they change.

Reference architecture (text):

A developer runs dotnet new co-service -n Company.Payments or picks the template in the developer portal. A new repo is created with source, tests, Dockerfile, infra/ Bicep and a pipeline file that calls the platform’s reusable workflow. On push, the pipeline authenticates to Azure using workload identity federation, restores packages from Azure Artifacts, builds with warnings as errors, runs unit and component tests, scans dependencies and the image, pushes the image to ACR, and deploys to a dev Container Apps environment. The deployed app pulls from ACR with a managed identity, reads secrets from Key Vault, exposes /health/live and /health/ready, and sends traces, metrics and logs to Application Insights and Log Analytics. A scheduled job compares each repo’s .template-version to the latest and reports lagging services.

Teaching guide for my team

Explain to a beginner in 2 minutes:

“When we start a new service, we do not start from an empty folder. We run one command and get a project that already builds, has a test, has a Dockerfile, and has a pipeline that runs. Everyone’s services look the same, so you can find things anywhere. Think of it as a standard floor plan for a house. You still choose the rooms’ contents, but the walls, wiring and fire exits are already right.”

Explain to an intermediate developer in 5 minutes:

Cover four points. First, what is in the template: layout, Directory.Build.props, analyzers, Dockerfile, test project, pipeline. Second, what is not: runtime behaviour, which lives in the chassis package. Third, how it is delivered: dotnet new package from our feed, with parameters such as service name and database type. Fourth, the drift problem: after generation the repo is yours, so shared pipeline logic is in reusable templates, and the template version is recorded so we can see who is behind. Finish by showing a real diff between two services created a year apart.

Hands-on exercise:

Task: Build a template called co-service and prove it works in CI.

  1. Create a folder with a minimal API project, a test project, a Dockerfile and .template.config/template.json using sourceName.
  2. Add Directory.Build.props with TreatWarningsAsErrors on.
  3. Run dotnet new install ., then dotnet new co-service -n Company.Policy in a temp folder.
  4. In the generated folder run dotnet build and dotnet test, then docker build ..
  5. Put steps 3 and 4 into a pipeline job that runs whenever the template changes.

Expected outcome: A generated Company.Policy service compiles, its health test passes, and the image builds and runs as a non-root user (docker run --rm <image> id shows a non-zero UID). If someone breaks the template, the pipeline fails before the broken template reaches teams.

Interview-style questions:

  1. What is the difference between a Service Template and a Microservice Chassis? The template is copied once at creation and provides structure and configuration files; the chassis is a versioned library referenced continuously and provides runtime behaviour. Updates to the chassis flow by version bump; updates to the template need explicit propagation.
  2. How do you stop services from drifting away from the template? Move fast-changing pieces into reusable pipeline templates and packages, record the template version in each repo, run scheduled reports and bot-created pull requests for updates, and test the template by generating and building a service in CI.
  3. When would you not create a Service Template? When there are only one or two services, when the stacks are too different to share one, or when there is no owner to maintain it. In those cases a good example repo and README are enough.

Mastery checklist

  • I can explain the difference between a Service Template, a chassis and reusable pipeline templates, with an example of what belongs in each.
  • I can create a dotnet new template with a sourceName and install and use it locally.
  • I can list the minimum contents of a .NET 10 service template (build props, analyzers, tests, non-root Dockerfile, health endpoints, pipeline).
  • I can add a CI job that generates a service from the template and builds and tests it.
  • I can describe at least three ways to reduce template drift and how to measure it.
  • I can explain why secrets and environment-specific values do not belong in a template.
  • I can map the template’s pipeline to Azure (ACR, Container Apps or AKS, Key Vault, OIDC federation, Application Insights).
  • I can write a short ADR justifying (or rejecting) a template for a given organisation size.

Key takeaway

A Service Template makes the right way the easy way at the moment a service is born, but it is a copy, so keep fast-changing logic in shared pipelines and packages and give the template an owner and a version.

Interactive Architectural Roadmaps

Explore Complete Roadmaps & Pattern Checklists

Track your learning with interactive checklists for all 23 Gang of Four patterns and modern Microservice architecture patterns.

Share:
Back to Blog

Related Posts

View All Posts
Microservices

Day 40: Microservice Chassis

A Microservice Chassis is a shared, versioned starter library (in .NET: a NuGet package, or a small set of them) that every service references so that logging, tracing, metrics, health checks, authentication, error...

Manikandan
Manikandan·13 min read
Microservices

Day 39: Externalized Configuration

Externalized Configuration means the compiled artifact (a container image or a published build) contains no environment-specific settings and no secrets.

Manikandan
Manikandan·15 min read
Microservices

Day 38: Access Token

The Access Token pattern means the caller's identity is verified once, at the edge (API Gateway or identity provider), and the result is captured in a signed, short-lived token (usually a JWT).

Manikandan
Manikandan·15 min read