Deploying 8 Microservices to AWS EKS with Docker Compose: A Step-by-Step Walkthrough
A walk through

Introduction
Spring PetClinic Microservices is a production-grade distributed application used by the Spring community to demonstrate microservices architecture. It consists of 8 independent services, a service registry, a config server, and a full observability stack including Prometheus, Grafana, and Zipkin.
As part of DMI Cohort 2, I deployed this application both locally using Docker Compose and to AWS EKS using Terraform and ArgoCD. This post documents the full process, the commands, the startup order, the observability stack, and the key lessons.
Prerequisites
Before starting, the following tools must be installed:
Docker Desktop (version 24+)
Git
AWS CLI (configured with appropriate IAM credentials)
Terraform 1.5+
kubectl
Helm 3
Part 1: Local Deployment with Docker Compose
Step 1: Clone the Repository
git clone https://github.com/Petcare-Clinic/petcareClinic-cloudops.git cd petcareClinic-cloudops
Confirm you are in the right directory: ls
You should see: docker-compose.yml, helm/, terraform/, upstream/, .github/
Step 2: Start All Services
docker compose up -d
This single command pulls all images, creates the network, and starts all containers in the correct order. The -d flag runs everything in detached mode.
Step 3: Verify All Containers Are Running
docker compose ps
You should see all containers with status Up. The most important thing to verify is that config-server and discovery-server show Healthy before the other 6 services start. This is enforced by the depends_on configuration in the Compose file.
The Startup Order — Why It Matters
The depends_on: condition: service_healthy configuration in docker-compose.yml means Docker will not start the API Gateway, Customers Service, Vets Service, Visits Service, GenAI Service, or Admin Server until both Config Server (port 8888) and Discovery Server (port 8761) have passed their health checks.
Config Server must start first because every other service reads its configuration from it on startup, database URLs, service ports, feature flags. If Config Server is not ready, the other services will fail to start correctly.
Discovery Server (Eureka) must start second because it is the service registry. Every service registers itself with Eureka on startup, and the API Gateway uses Eureka to discover the addresses of the services it routes traffic to. Without Eureka, the API Gateway cannot route requests.
This is the difference between docker compose up (starts all services together, respecting depends_on) and docker compose down (stops and removes all containers and the network, cleaning up the entire environment).
Step 4: Verify the Application
Open each URL to confirm all services are running:
http://localhost:8080 — Spring PetClinic main app (find owners, view pets, book visits)
http://localhost:8761 — Eureka dashboard (should show all 8 services registered)
http://localhost:9090 — Spring Boot Admin (health status of all services)
http://localhost:9411 — Zipkin distributed tracing
http://localhost:9091 — Prometheus metrics
http://localhost:3030 — Grafana dashboards
Step 5: Functional Test
Navigate to localhost:8080, click Find Owners, and search without a filter. You should see a list of pre-loaded owners. Click an owner to view their pets and visit history. Add a visit by clicking a pet, selecting Add Visit, entering a date and description, and submitting. Confirm it appears in the history.
Navigate to Veterinarians to confirm the table of vets with their specialties loads correctly.
Part 2: The Observability Stack
Prometheus
Prometheus scrapes the /actuator/prometheus endpoint on each service every 15 seconds. Run this query in the Prometheus UI to see request counts across all services:
http_server_requests_seconds_count
You will see time-series data broken down by service, endpoint, HTTP method, and status code.
Grafana
The Grafana instance at localhost:3030 is pre-configured with a Prometheus datasource and the Spring Petclinic Metrics dashboard. Default credentials are admin / petclinic. The dashboard shows HTTP request rates, JVM heap memory usage per service, pod restart counts, and database connection pool health in real time.
Zipkin
Zipkin at localhost:9411 shows distributed traces. Each trace represents a single request flowing through the system. A request to GET /api/customer/owners, for example, will show the API Gateway receiving the request, routing it to Customers Service, and Customers Service querying the database — all as a single connected trace with timing for each step.
Part 3: AWS Deployment
Infrastructure Provisioning with Terraform
cd terraform terraform init terraform plan terraform apply -auto-approve
This creates the full AWS environment: VPC with public and private subnets, NAT gateway, Amazon EKS cluster (petclinic-cluster in us-east-1), ECR repositories for each of the 8 services, and all IAM roles with least-privilege policies.
GitOps Deployment with ArgoCD
Once the cluster is running, the bootstrap script sets up ArgoCD and registers the application:
bash scripts/bootstrap-argocd.sh
ArgoCD is configured to watch the Helm values files in the repository. When the CI/CD pipeline pushes a new image tag to values.yaml, ArgoCD detects the change and automatically syncs the cluster — pulling the new image from ECR and performing a rolling update with zero downtime.
Key AWS Challenge
The most challenging part of the AWS deployment was ensuring the EKS node group had sufficient IAM permissions to pull images from ECR without exposing credentials in the application. The solution was using IRSA (IAM Roles for Service Accounts) to attach the AmazonEC2ContainerRegistryReadOnly policy directly to the Kubernetes service account, so pods can pull images without any credentials in the manifest or environment variables.
Key Lessons
The startup order is not optional in distributed systems. Config Server and Discovery Server are dependencies of everything else — treat them like infrastructure, not application services.
Observability before features. Getting Prometheus and Grafana running before the first deployment meant we could see problems before users reported them.
Docker Compose and Kubernetes are not interchangeable. Compose is excellent for local development where startup order and port mapping are predictable. Kubernetes is for production where you need self-healing, auto-scaling, rolling updates, and multi-node scheduling.
Conclusion
This project is part of DMI Cohort 2, a DevOps Micro Internship programme focused on hands-on, production-grade engineering. If you want to build real skills in cloud engineering and DevOps in a team environment, DMI Cohort 3 starts 27 June 2026:
https://docs.google.com/forms/d/e/1FAIpQLSel7ai7nyb0P1qLW4vEyfB_nEsD4lUF1XG88vmAaFGBOb6hPA/viewform



