Before you automate something, you need to understand it. Before my team deployed Spring PetClinic to AWS EKS with Terraform, Helm, and ArgoCD, I ran it on a single EC2 instance using Docker and Docker Compose. This was a deliberate first step. Getting it running manually first meant I could see how the services depend on each other, what healthy looked like, and what broke when the startup order was wrong, things the automated setup handles silently.
This post documents exactly what I did, what came up, and what the observability stack showed once everything was running.
What is Spring PetClinic?
Spring PetClinic is a sample application built by the Spring team to demonstrate Spring Boot features. The microservices version splits the app into eight independent services: Config Server, Discovery Server (Eureka), API Gateway, Customers Service, Visits Service, Vets Service, GenAI Service, and Admin Server.
Each service runs in its own container. They communicate over a shared Docker network, with Eureka handling service discovery and the Config Server providing centralised configuration. The whole stack also ships with Prometheus, Grafana, and Zipkin for observability.
Prerequisites
Before starting, you need:
- An AWS EC2 instance (I used Ubuntu 24.04, t2.medium or larger, the full stack is memory-heavy)
- Ports open in your security group: 8080, 8761, 9090, 9091, 3030, 9411
- SSH access to the instance
- Git installed (commonly pre-installed on Linux systems; verify with
git --version)
Step 1: Install Docker and Docker Compose
SSH into your EC2 instance and run:
sudo apt update
sudo apt install docker.io docker-compose-v2 -y
docker --version
docker compose version
Start and enable Docker so it runs on boot:
sudo systemctl start docker
sudo systemctl enable docker
sudo systemctl status docker
Add your user to the Docker group so you can run commands without sudo:
sudo usermod -aG docker ubuntu
newgrp docker
docker ps
The newgrp docker command applies the group change without requiring a logout. Running docker ps confirms Docker is accessible without sudo.
Step 2: Clone the Repository
git clone https://github.com/ntando-mv15/spring-petclinic-microservices.git
cd spring-petclinic-microservices
ls
The ls confirms you're in the right directory and the docker-compose.yml is present before running anything.
Step 3: Start the Services
docker compose up -d
docker ps
docker compose up lets you start all the services with a single command. The -d flag runs all containers in detached mode.
docker ps lets you confirm all containers started, you're looking for all eight services plus the observability containers showing as Up.
The Startup Order: Why It Matters
This is something worth understanding before you run the stack for the first time.
- The Config Server must start first because every other service fetches its configuration from it on startup. If the Config Server isn't ready, the other services fail to initialise.
- The Discovery Server (Eureka) starts second. It acts as a service registry, every service registers itself here so other services can find it by name rather than hardcoded IP. If Eureka isn't up when another service starts, that service can't register and won't be reachable.
- The API Gateway comes after both, since it needs to know where the downstream services are registered before it can route traffic to them.
Docker Compose handles this dependency order via the depends_on configuration in docker-compose.yml. In practice, even with depends_on, some services may need a minute or two to fully initialise after their container starts. If the app isn't responding immediately, wait and check docker ps again.
Step 4: Verify the Deployment
Once all containers are up, open your browser and navigate to http://<your-ec2-public-ip>:8080. You should see the PetClinic welcome page.
Application homepage: The app loaded at port 8080 via the API Gateway. Navigation works: Find Owners, Register Owner, and Veterinarians are all accessible.
Owner and pet records: I navigated to an existing owner profile (Maria Escobito at 345 Maple St.) and confirmed that pet and visit data were loading correctly from the Customers and Visits services via the API Gateway. I also confirmed that I could update her data and add a visit.
Spring Boot Admin at port 9090 showed all five registered services, Admin Server, API Gateway, Customers Service, Vets Service, and Visits Service, all reporting UP with a green checkmark and timestamp.
Eureka Dashboard at port 8761 showed the same five services registered with their instance IDs and ports, confirming service discovery was working. It also showed server stats: 87MB available memory, 59MB in use (67%), and 2 CPUs.
The Observability Stack
Prometheus at port 9091 was actively scraping metrics from the running services. I ran two queries:
-
system_cpu_usage- showed CPU usage across four services (vets, api-gateway, visits, customers). There was a visible spike at startup as the services initialised, which then settled to a flat baseline of around 0.03–0.05. -
http_server_requests_seconds_count- showed cumulative HTTP request counts across 26 time series, broken down by service, method, URI, and outcome. The steady upward slope confirmed Prometheus was receiving traffic from all scraped services.
Grafana at port 3030 showed the Spring PetClinic Metrics dashboard with live data:
- HTTP request latency
- HTTP request activity
- Business counters: 2 owners created, 4 visits created during my testing session
- The SPC Business Histogram at the bottom showed consistent request distribution over the session window
Zipkin at port 9411 was running and accessible, with trace search available by service name, duration, and tag query. I confirmed the UI was up and accepting queries.
Key Takeaway
The biggest thing this deployment taught me was how dependent services are on startup order. Kubernetes handles this with readiness probes and init containers. Docker Compose handles it with depends_on, but that only waits for the container to start, not for the application inside it to be ready. In practice, you may need to wait a minute after docker compose up before the full stack is healthy.
Running the observability stack locally also gave me a concrete reference point for what "normal" looks like, what CPU usage, request rates, and latency look like at rest with no load. That baseline matters when you're later trying to decide whether an alert threshold is sensible.
This deployment is part of my work on the DevOps Micro Internship (DMI) Cohort 2 by Pravin Mishra. 🚀 DMI Cohort 3 starts 27 June 2026 - apply here if you want hands-on experience with DevOps and AWS






Top comments (0)