<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: amrali21</title>
    <description>The latest articles on DEV Community by amrali21 (@amrali21).</description>
    <link>https://dev.to/amrali21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4092988%2F9f608b48-6254-4799-b867-5c106ae9a427.jpg</url>
      <title>DEV Community: amrali21</title>
      <link>https://dev.to/amrali21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amrali21"/>
    <language>en</language>
    <item>
      <title>I built a simplified microservice reference project with guide on how to run locally, using docker-compose or Kubernetes + deployment to AKS</title>
      <dc:creator>amrali21</dc:creator>
      <pubDate>Tue, 25 Aug 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/amrali21/i-built-a-simplified-microservice-reference-project-with-guide-on-how-to-run-locally-using-13f6</link>
      <guid>https://dev.to/amrali21/i-built-a-simplified-microservice-reference-project-with-guide-on-how-to-run-locally-using-13f6</guid>
      <description>&lt;p&gt;Today I'm showcasing a project I've been working on for a while: Ledgerly, a simplified microservices reference project built with .NET and Angular.&lt;/p&gt;

&lt;p&gt;I built Ledgerly to be be focused purely on microservices best practices.&lt;/p&gt;

&lt;p&gt;What it demonstrates: &lt;br&gt;
🔹 Database-per-service — 4 services, 4 independent databases &lt;/p&gt;

&lt;p&gt;🔹 gRPC for synchronous service-to-service calls &lt;/p&gt;

&lt;p&gt;🔹 RabbitMQ for async events — invoice status changes publish events that a dashboard service consumes to keep KPIs in sync, with zero direct coupling &lt;/p&gt;

&lt;p&gt;🔹 API gateway + service discovery (Eureka locally/Docker, Kubernetes DNS in K8s) &lt;/p&gt;

&lt;p&gt;🔹 JWT auth issued by its own service&lt;/p&gt;

&lt;p&gt;It also ships as a full deployment guide — run it locally, in Docker Compose, or on Kubernetes, including a walkthrough for deploying the cluster to Azure (AKS).&lt;br&gt;
Built as a boilerplate: fork it, swap in your own domain, keep the architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Services:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmdga1dnc0g38ar0sl04w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmdga1dnc0g38ar0sl04w.gif" alt="description of gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Databases
&lt;/h3&gt;

&lt;p&gt;Each of the four .NET services has its &lt;strong&gt;own&lt;/strong&gt; SQL Server database — there is&lt;br&gt;
no shared database. &lt;strong&gt;All four databases live on the host machine&lt;/strong&gt;, even&lt;br&gt;
when the services themselves run in Docker or Kubernetes — this keeps setup&lt;br&gt;
simple (no containerized SQL Server to manage):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Invoice service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ledgerly-invoice&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ledgerly-customer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ledgerly-dashbaord&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ledgerly-auth&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  How service discovery works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local / Docker:&lt;/strong&gt; services register themselves with &lt;strong&gt;Eureka&lt;/strong&gt;, and the
Ocelot gateway resolves downstream services by name through Eureka.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes:&lt;/strong&gt; Eureka is removed. Each service is exposed by a Kubernetes
&lt;strong&gt;Service&lt;/strong&gt; (ClusterIP), and the gateway routes directly to those service
names. ClusterIP load-balances across pods.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Install these on your host machine before running the project, in any mode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;JDK&lt;/strong&gt; (Java 11+) — to build/run the Eureka server (&lt;code&gt;eureka-server/&lt;/code&gt;).
Needed for local and Docker modes; Kubernetes doesn't use Eureka.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL Server Express&lt;/strong&gt; — all four databases run on the host in every mode
(local, Docker, and Kubernetes). See Databases above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET 10 SDK&lt;/strong&gt; (Developer Pack) — to build/run the services locally. Note:
&lt;code&gt;ledgerly-backend-invoice-service&lt;/code&gt; targets &lt;code&gt;net6.0&lt;/code&gt;, so the matching .NET 6
runtime is required alongside .NET 10.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RabbitMQ&lt;/strong&gt;, via Chocolatey — needed for local mode only (Docker Compose
and Kubernetes each run their own RabbitMQ container):
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rabbitmq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-y&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This pulls in Erlang as a dependency — &lt;strong&gt;restart the RabbitMQ service&lt;/strong&gt;&lt;br&gt;
   afterwards so it picks up the Erlang install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Restart-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RabbitMQ&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create the databases&lt;/strong&gt; — run every script in &lt;a href="https://dev.todb/"&gt;db/&lt;/a&gt; against your SQL
Server instance (creates the four databases, schema, and seed data) before
starting any service.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Running the project
&lt;/h2&gt;

&lt;p&gt;You can run the stack in three modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Local (dotnet + Eureka)
&lt;/h3&gt;

&lt;p&gt;Start the Eureka discovery server first, then launch all the .NET services and&lt;br&gt;
the frontend with the batch script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Start Eureka (in its own terminal; leave it running)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eureka-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bootRun&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# 2. From the repo root, build + start all services and the Angular frontend&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;run-all.bat&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#   run-all.bat nobuild   # skip the build, just start everything&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;run-all.bat&lt;/code&gt; opens a separate window per service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invoice  → &lt;code&gt;https://localhost:7052&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;customer → &lt;code&gt;https://localhost:7099&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;dashboard→ &lt;code&gt;https://localhost:7063&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;auth     → &lt;code&gt;https://localhost:7109&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;gateway  → &lt;code&gt;https://localhost:7019&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;frontend → &lt;code&gt;http://localhost:4200&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Connection string:&lt;/strong&gt; each service reads &lt;code&gt;ConnectionStrings:DefaultConnection&lt;/code&gt;&lt;br&gt;
from its own &lt;code&gt;appsettings.json&lt;/code&gt; (e.g.&lt;br&gt;
&lt;code&gt;ledgerly-backend-invoice-service/appsettings.json&lt;/code&gt;). Update the &lt;code&gt;Data Source&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;User ID&lt;/code&gt;, and &lt;code&gt;Password&lt;/code&gt; there to match your local SQL Server Express&lt;br&gt;
instance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Docker Compose
&lt;/h3&gt;

&lt;p&gt;Builds and runs every service — &lt;strong&gt;including Eureka&lt;/strong&gt; — as containers on a shared&lt;br&gt;
network. The database stays on the host machine (&lt;code&gt;host.docker.internal\SQLEXPRESS&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# DB credentials come from a .env file (SQL_USER / SQL_PASSWORD)&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exposed ports: frontend &lt;code&gt;4200&lt;/code&gt;, gateway &lt;code&gt;8080&lt;/code&gt;, invoice &lt;code&gt;5052&lt;/code&gt;, customer &lt;code&gt;5246&lt;/code&gt;,&lt;br&gt;
dashboard &lt;code&gt;5208&lt;/code&gt;, auth &lt;code&gt;5109&lt;/code&gt;, Eureka &lt;code&gt;8761&lt;/code&gt;. Services wait for Eureka to be&lt;br&gt;
healthy before starting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Connection string:&lt;/strong&gt; copy &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and set &lt;code&gt;SQL_USER&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;SQL_PASSWORD&lt;/code&gt;, plus one connection string per service — &lt;code&gt;INVOICE_DB&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;CUSTOMER_DB&lt;/code&gt;, &lt;code&gt;DASHBOARD_DB&lt;/code&gt;, &lt;code&gt;AUTH_DB&lt;/code&gt;. These are injected into each&lt;br&gt;
container as &lt;code&gt;ConnectionStrings__DefaultConnection&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  3. Kubernetes (Local + Deploy to AKS)
&lt;/h3&gt;

&lt;p&gt;On Kubernetes &lt;strong&gt;Eureka is not used&lt;/strong&gt;. Instead, each deployment is exposed by a&lt;br&gt;
Kubernetes Service, and the API gateway routes traffic directly to those&lt;br&gt;
services (Kubernetes DNS + ClusterIP handle discovery and load-balancing).&lt;br&gt;
Every service has its &lt;strong&gt;own Deployment&lt;/strong&gt;, so each can be scaled independently&lt;br&gt;
for maximum scalability (with HPAs autoscaling on CPU).&lt;/p&gt;

&lt;p&gt;A single &lt;strong&gt;Ingress&lt;/strong&gt; is what allows connections into the Kubernetes cluster from&lt;br&gt;
outside — it is the only public entry point. It routes by path, and the most&lt;br&gt;
important entry point is &lt;strong&gt;&lt;code&gt;/&lt;/code&gt;&lt;/strong&gt;, which points to the Angular frontend (the&lt;br&gt;
&lt;code&gt;/InvoiceGW&lt;/code&gt;, &lt;code&gt;/CustomerGW&lt;/code&gt;, &lt;code&gt;/Dashboard&lt;/code&gt;, &lt;code&gt;/AuthGW&lt;/code&gt; paths go to the gateway).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Connection string:&lt;/strong&gt; copy &lt;code&gt;k8s/01-db-secret.example.yaml&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;k8s/01-db-secret.yaml&lt;/code&gt; and fill in the &lt;code&gt;ConnectionStrings__DefaultConnection&lt;/code&gt;&lt;br&gt;
value in each of the four secrets. For local minikube testing, point at&lt;br&gt;
&lt;code&gt;host.minikube.internal,1433&lt;/code&gt; (not the &lt;code&gt;\SQLEXPRESS&lt;/code&gt; named instance, and not&lt;br&gt;
an AKS/cloud DB) — see the comments in the example file for the one-time SQL&lt;br&gt;
Server Express config this requires.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-k&lt;/span&gt; k8s/

&lt;span class="c"&gt;# route local traffic to the ingress controller (instead of using `minikube tunnel`)&lt;/span&gt;
kubectl port-forward &lt;span class="nt"&gt;-n&lt;/span&gt; ingress-nginx service/ingress-nginx-controller 8090:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the app at &lt;strong&gt;&lt;a href="http://myapp.local:8090" rel="noopener noreferrer"&gt;http://myapp.local:8090&lt;/a&gt;&lt;/strong&gt; (after mapping &lt;code&gt;myapp.local&lt;/code&gt;&lt;br&gt;
to the minikube IP in your hosts file — see the k8s README).&lt;/p&gt;

&lt;p&gt;See &lt;strong&gt;&lt;a href="//k8s/README.md"&gt;k8s/README.md&lt;/a&gt;&lt;/strong&gt; for full details — image builds, the DB&lt;/p&gt;

&lt;p&gt;The project🔗: [&lt;a href="https://lnkd.in/d_NeksRk" rel="noopener noreferrer"&gt;https://lnkd.in/d_NeksRk&lt;/a&gt;]&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
