DEV Community

Aftab Bashir
Aftab Bashir

Posted on

I built a Mini Internal Developer Platform in .NET — GitHub repos + Kubernetes deployments from a single API call

Most teams I've worked with waste 2-3 hours every time they spin up a
new microservice. Create the repo, write the Dockerfile, set up CI,
write Kubernetes manifests, configure ingress, same boilerplate, every
single time.

I decided to automate all of it. One API call. Everything done
automatically.

What it does

POST to /api/services with a service name and language, and the
platform automatically:

  • Creates a GitHub repository
  • Commits a Dockerfile and GitHub Actions CI pipeline
  • Creates a Kubernetes Deployment, Service, and Ingress
  • Streams real-time status updates to a live dashboard via SignalR

The HTTP response comes back in under 200ms. All the work happens in a
background worker.

The architecture

The system is split into four .NET projects:

  • Idp.Api — ASP.NET Core 9 API, the entry point
  • Idp.Core — domain models and interfaces
  • Idp.Infrastructure — GitHub and Kubernetes integrations
  • Idp.Worker — background provisioning pipeline

When a request comes in, the controller does one thing — saves a record
to PostgreSQL with status queued and returns 202 Accepted. The
background worker polls every 5 seconds, picks up queued jobs, and runs
the full provisioning pipeline.

The GitHub integration

I used Octokit.net to talk to the GitHub REST API. Here is what happens
when you provision a new service:

var repo = await client.Repository.Create(new NewRepository(serviceName)
{
    Description = description,
    Private     = false,
    AutoInit    = true
});

// Give GitHub a moment to initialise
await Task.Delay(2000, ct);

// Commit Dockerfile
await client.Repository.Content.CreateFile(
    organisation, serviceName, "Dockerfile",
    new CreateFileRequest("chore: add Dockerfile", GetDockerfile(language)));

// Commit CI workflow
await client.Repository.Content.CreateFile(
    organisation, serviceName, ".github/workflows/ci.yml",
    new CreateFileRequest("chore: add CI workflow", GetCiWorkflow()));
Enter fullscreen mode Exit fullscreen mode

The AutoInit: true creates an initial commit so we can push files
immediately. The 2 second delay is important — without it, GitHub
sometimes returns a 404 when you try to commit to a freshly created repo.

The Kubernetes integration

I used the official KubernetesClient NuGet package to create K8s
objects from C#:

// Create Deployment
await client.AppsV1.CreateNamespacedDeploymentAsync(deployment, ns);

// Create Service
await client.CoreV1.CreateNamespacedServiceAsync(service, ns);

// Create Ingress
await client.NetworkingV1.CreateNamespacedIngressAsync(ingress, ns);
Enter fullscreen mode Exit fullscreen mode

Each service gets its own namespace, which keeps things clean and makes
it easy to delete a service and all its resources in one command.

Real-time updates with SignalR

The background worker pushes status updates to all connected browsers
as provisioning progresses:

await hubContext.Clients.All.SendAsync("StatusChanged", new
{
    ServiceId   = serviceId,
    ServiceName = serviceName,
    Status      = status,  // queued → creating_repo → deploying_k8s → deployed
    RepoUrl     = repoUrl,
    ServiceUrl  = serviceUrl
});
Enter fullscreen mode Exit fullscreen mode

The dashboard listens for these events and updates the UI in real time
— no polling, no page refresh.

What I learned

Always use async provisioning. The first version was synchronous —
the HTTP request sat open for 6 seconds while GitHub did its work.
Moving to a background worker with a queue made the API feel instant.

GitHub needs a moment after repo creation. If you immediately try
to commit files to a freshly created repo, you get a 404. A 2 second
delay after AutoInit fixes it reliably.

Handle conflicts gracefully in Kubernetes. If you try to create a
resource that already exists, K8s returns a 409 Conflict. Catching that
and either replacing or skipping makes the system idempotent.

Secrets in git history are permanent. I accidentally committed my
GitHub token inside the bin/ folder early on. Even after deleting it,
it was in git history. I had to force push a rewritten history and
immediately rotate the token. Always add **/bin/ and **/obj/ to
.gitignore before your first commit.

The stack

Layer Technology
API ASP.NET Core 9
Database PostgreSQL + Entity Framework Core 9
GitHub automation Octokit.net
Kubernetes automation KubernetesClient
Real-time updates SignalR
Logging Serilog
API docs Scalar

Try it yourself

The full source code is on GitHub:
https://github.com/aftabkh4n/idp-platform

Clone it, add your GitHub token to appsettings.json, spin up a
PostgreSQL container, and run it. You should have a working IDP in under
5 minutes.


This is one of four portfolio projects I am building to demonstrate
senior backend engineering skills. Next up: a full Data Platform API
with search, analytics, and recommendations.

If you found this useful or have questions, drop a comment below.

Top comments (0)