Galleri5 Technologies
Context: Galleri5 is a creator economy platform serving enterprise clients like L'Oreal, HUL,shein, and AJIO. Our backend is FastAPI + MongoDB + Redis, currently running on GCP. We're planning a migration to Azure.
You'll be working with a simplified version of our platform — 3 services that work together:
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
│ Creator API │──────▶│ Analytics Service │──────▶│ Worker │
│ (FastAPI) │ │ (FastAPI) │ │ (Background) │
│ Port 8000 │ │ Port 8001 │ │ │
└────────┬────────┘ └─────────┬───────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ MongoDB │ │ Redis │ │ Redis │
│ │ │ (cache) │ │ (queue) │
└─────────┘ └─────────┘ └─────────┘
- Creator API — Main API for managing creators and campaigns. Uses MongoDB for storage, Redis for caching, and calls Analytics Service for stats.
- Analytics Service — Receives events from Creator API, stores analytics data in Redis, pushes tasks to a worker queue.
- Worker — Background process that consumes events from Redis queue and does heavy processing (computing engagement scores, generating reports).
The application code is provided below. Do not modify the app logic — your job is everything around it (containerization, infrastructure, deployment, monitoring).
"""Galleri5 Creator API — Main serviceHandles creator CRUD and campaign management.Talks to MongoDB for storage, Redis for caching, and Analytics Service for stats."""fromfastapiimportFastAPI, HTTPException, BackgroundTasksfrompydanticimportBaseModelfromtypingimportOptionalimportosimporttimeimporthttpximportjsonimportlogginglogging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger=logging.getLogger("creator-api")
app=FastAPI(title="Galleri5 Creator API", version="1.0.0")
# --- Config ---MONGO_URI=os.getenv("MONGODB_URI", "mongodb://mongo:27017")
REDIS_HOST=os.getenv("REDIS_HOST", "redis")
REDIS_PORT=int(os.getenv("REDIS_PORT", "6379"))
ANALYTICS_SERVICE_URL=os.getenv("ANALYTICS_SERVICE_URL", "http://analytics-service:8001")
API_SECRET=os.getenv("API_SECRET", "")
ENVIRONMENT=os.getenv("ENVIRONMENT", "development")
try:
frommotor.motor_asyncioimportAsyncIOMotorClientmongo_client=AsyncIOMotorClient(MONGO_URI, serverSelectionTimeoutMS=3000)
db=mongo_client.galleri5MONGO_AVAILABLE=TrueexceptImportError:
mongo_client=Nonedb=NoneMONGO_AVAILABLE=Falselogger.warning("motor not installed — MongoDB features disabled")
try:
importredisasredis_libredis_client=redis_lib.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, socket_timeout=3)
REDIS_AVAILABLE=TrueexceptImportError:
redis_client=NoneREDIS_AVAILABLE=Falselogger.warning("redis not installed — caching disabled")
# --- In-memory data ---CREATORS_DATA= {
"c001": {"id": "c001", "name": "Priya Sharma", "platform": "instagram", "followers": 125000, "category": "fashion", "engagement_rate": 3.2},
"c002": {"id": "c002", "name": "Rohan Mehta", "platform": "youtube", "followers": 890000, "category": "tech", "engagement_rate": 4.1},
"c003": {"id": "c003", "name": "Ananya Reddy", "platform": "instagram", "followers": 45000, "category": "beauty", "engagement_rate": 5.8},
"c004": {"id": "c004", "name": "Vikram Das", "platform": "youtube", "followers": 2100000, "category": "gaming", "engagement_rate": 2.9},
"c005": {"id": "c005", "name": "Meera Joshi", "platform": "instagram", "followers": 310000, "category": "food", "engagement_rate": 4.5},
}
CAMPAIGNS_DATA= {
"camp01": {"id": "camp01", "client": "L'Oreal", "name": "Summer Beauty 2025", "status": "active", "budget": 500000, "creators": ["c001", "c003"]},
"camp02": {"id": "camp02", "client": "HUL", "name": "Social Trends Q1", "status": "active", "budget": 750000, "creators": ["c002", "c005"]},
"camp03": {"id": "camp03", "client": "AJIO", "name": "Fashion Forward", "status": "completed", "budget": 300000, "creators": ["c001", "c004"]},
}
START_TIME=time.time()
classCreatorCreate(BaseModel):
name: strplatform: strfollowers: intcategory: strengagement_rate: float=0.0classCampaignCreate(BaseModel):
client: strname: strbudget: floatcreator_ids: list[str]
defcache_get(key: str):
ifnotREDIS_AVAILABLEornotredis_client:
returnNonetry:
val=redis_client.get(key)
returnjson.loads(val) ifvalelseNoneexceptExceptionase:
logger.warning(f"Redis GET failed: {e}")
returnNonedefcache_set(key: str, value: dict, ttl: int=300):
ifnotREDIS_AVAILABLEornotredis_client:
returntry:
redis_client.setex(key, ttl, json.dumps(value))
exceptExceptionase:
logger.warning(f"Redis SET failed: {e}")
asyncdefnotify_analytics(event_type: str, data: dict):
try:
asyncwithhttpx.AsyncClient(timeout=5) asclient:
awaitclient.post(
f"{ANALYTICS_SERVICE_URL}/events",
json={"event_type": event_type, "data": data, "timestamp": time.time()}
)
exceptExceptionase:
logger.warning(f"Analytics notification failed: {e}")
@app.get("/health")asyncdefhealth():
checks= {
"status": "healthy",
"version": os.getenv("APP_VERSION", "1.0.0"),
"environment": ENVIRONMENT,
"uptime_seconds": round(time.time() -START_TIME, 2),
}
ifMONGO_AVAILABLEandmongo_client:
try:
awaitmongo_client.admin.command("ping")
checks["mongodb"] ="connected"exceptException:
checks["mongodb"] ="disconnected"checks["status"] ="degraded"else:
checks["mongodb"] ="not_configured"ifREDIS_AVAILABLEandredis_client:
try:
redis_client.ping()
checks["redis"] ="connected"exceptException:
checks["redis"] ="disconnected"checks["status"] ="degraded"else:
checks["redis"] ="not_configured"try:
asyncwithhttpx.AsyncClient(timeout=3) asclient:
resp=awaitclient.get(f"{ANALYTICS_SERVICE_URL}/health")
checks["analytics_service"] ="connected"ifresp.status_code==200else"unhealthy"exceptException:
checks["analytics_service"] ="disconnected"returnchecks@app.get("/creators")asyncdeflist_creators(platform: Optional[str] =None, category: Optional[str] =None, min_followers: Optional[int] =None):
cache_key=f"creators:{platform}:{category}:{min_followers}"cached=cache_get(cache_key)
ifcached:
logger.info(f"Cache HIT: {cache_key}")
returncachedresults=list(CREATORS_DATA.values())
ifplatform:
results= [cforcinresultsifc["platform"] ==platform.lower()]
ifcategory:
results= [cforcinresultsifc["category"] ==category.lower()]
ifmin_followers:
results= [cforcinresultsifc["followers"] >=min_followers]
response= {"creators": results, "count": len(results), "cached": False}
cache_set(cache_key, response, ttl=120)
returnresponse@app.get("/creators/{creator_id}")asyncdefget_creator(creator_id: str):
cached=cache_get(f"creator:{creator_id}")
ifcached:
returncachedifcreator_idnotinCREATORS_DATA:
raiseHTTPException(status_code=404, detail=f"Creator {creator_id} not found")
creator=CREATORS_DATA[creator_id]
cache_set(f"creator:{creator_id}", creator, ttl=300)
returncreator@app.post("/creators")asyncdefcreate_creator(creator: CreatorCreate, background_tasks: BackgroundTasks):
creator_id=f"c{len(CREATORS_DATA) +1:03d}"new_creator= {"id": creator_id, **creator.dict()}
CREATORS_DATA[creator_id] =new_creatorbackground_tasks.add_task(notify_analytics, "creator_added", new_creator)
logger.info(f"Created creator: {creator_id} — {creator.name}")
returnnew_creator@app.get("/campaigns")asyncdeflist_campaigns(status: Optional[str] =None, client: Optional[str] =None):
results=list(CAMPAIGNS_DATA.values())
ifstatus:
results= [cforcinresultsifc["status"] ==status.lower()]
ifclient:
results= [cforcinresultsifc["client"].lower() ==client.lower()]
return {"campaigns": results, "count": len(results)}
@app.get("/campaigns/{campaign_id}")asyncdefget_campaign(campaign_id: str):
ifcampaign_idnotinCAMPAIGNS_DATA:
raiseHTTPException(status_code=404, detail=f"Campaign {campaign_id} not found")
campaign=CAMPAIGNS_DATA[campaign_id].copy()
campaign["creator_details"] = [CREATORS_DATA[cid] forcidincampaign["creators"] ifcidinCREATORS_DATA]
returncampaign@app.post("/campaigns")asyncdefcreate_campaign(campaign: CampaignCreate, background_tasks: BackgroundTasks):
invalid= [cidforcidincampaign.creator_idsifcidnotinCREATORS_DATA]
ifinvalid:
raiseHTTPException(status_code=400, detail=f"Invalid creator IDs: {invalid}")
campaign_id=f"camp{len(CAMPAIGNS_DATA) +1:02d}"new_campaign= {"id": campaign_id, "client": campaign.client, "name": campaign.name, "status": "active", "budget": campaign.budget, "creators": campaign.creator_ids}
CAMPAIGNS_DATA[campaign_id] =new_campaignbackground_tasks.add_task(notify_analytics, "campaign_created", new_campaign)
logger.info(f"Created campaign: {campaign_id} — {campaign.name} for {campaign.client}")
returnnew_campaign@app.get("/analytics/summary")asyncdefanalytics_summary():
cached=cache_get("analytics:summary")
ifcached:
returncachedtry:
asyncwithhttpx.AsyncClient(timeout=10) asclient:
resp=awaitclient.get(f"{ANALYTICS_SERVICE_URL}/summary")
ifresp.status_code==200:
data=resp.json()
cache_set("analytics:summary", data, ttl=60)
returndataexceptExceptionase:
logger.error(f"Analytics service unavailable: {e}")
total=len(CREATORS_DATA)
total_followers=sum(c["followers"] forcinCREATORS_DATA.values())
return {"source": "local_fallback", "total_creators": total, "total_followers": total_followers, "avg_followers": total_followers//totaliftotal>0else0, "active_campaigns": len([cforcinCAMPAIGNS_DATA.values() ifc["status"] =="active"])}Requirements (creator-api/requirements.txt):
fastapi==0.115.0
uvicorn[standard]==0.30.0
pydantic==2.9.0
httpx==0.27.0
motor==3.6.0
redis==5.1.0
"""Galleri5 Analytics ServiceReceives events from Creator API, computes aggregations.Stores event data in Redis and serves analytics endpoints."""fromfastapiimportFastAPIfromtypingimportOptionalimportosimporttimeimportjsonimportlogginglogging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger=logging.getLogger("analytics-service")
app=FastAPI(title="Galleri5 Analytics Service", version="1.0.0")
REDIS_HOST=os.getenv("REDIS_HOST", "redis")
REDIS_PORT=int(os.getenv("REDIS_PORT", "6379"))
ENVIRONMENT=os.getenv("ENVIRONMENT", "development")
try:
importredisasredis_libredis_client=redis_lib.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, socket_timeout=3)
REDIS_AVAILABLE=TrueexceptImportError:
redis_client=NoneREDIS_AVAILABLE=FalseSTART_TIME=time.time()
event_counters= {"creator_added": 0, "campaign_created": 0, "total_events": 0}
recent_events= []
@app.get("/health")defhealth():
checks= {"status": "healthy", "service": "analytics", "version": os.getenv("APP_VERSION", "1.0.0"), "environment": ENVIRONMENT, "uptime_seconds": round(time.time() -START_TIME, 2)}
ifREDIS_AVAILABLEandredis_client:
try:
redis_client.ping()
checks["redis"] ="connected"exceptException:
checks["redis"] ="disconnected"checks["status"] ="degraded"else:
checks["redis"] ="not_configured"returnchecks@app.post("/events")asyncdefingest_event(event: dict):
event_type=event.get("event_type", "unknown")
timestamp=event.get("timestamp", time.time())
logger.info(f"Event received: {event_type}")
event_counters["total_events"] +=1event_counters[event_type] =event_counters.get(event_type, 0) +1ifREDIS_AVAILABLEandredis_client:
try:
redis_client.incr(f"analytics:count:{event_type}")
redis_client.incr("analytics:count:total")
redis_client.lpush("analytics:recent_events", json.dumps({"event_type": event_type, "timestamp": timestamp, "data_summary": str(event.get("data", {}))[:200]}))
redis_client.ltrim("analytics:recent_events", 0, 99)
redis_client.lpush("analytics:worker_queue", json.dumps(event))
exceptExceptionase:
logger.warning(f"Redis write failed: {e}")
recent_events.append({"event_type": event_type, "timestamp": timestamp})
iflen(recent_events) >100:
recent_events.pop(0)
return {"status": "accepted", "event_type": event_type}
@app.get("/summary")asyncdefget_summary():
ifREDIS_AVAILABLEandredis_client:
try:
return {"source": "redis", "total_events": int(redis_client.get("analytics:count:total") or0), "creators_added": int(redis_client.get("analytics:count:creator_added") or0), "campaigns_created": int(redis_client.get("analytics:count:campaign_created") or0)}
exceptException:
passreturn {"source": "memory", **event_counters}
@app.get("/events/recent")asyncdefget_recent_events(limit: int=20):
ifREDIS_AVAILABLEandredis_client:
try:
events=redis_client.lrange("analytics:recent_events", 0, limit-1)
return {"events": [json.loads(e) foreinevents], "source": "redis"}
exceptException:
passreturn {"events": recent_events[-limit:], "source": "memory"}
@app.get("/queue/size")asyncdefget_queue_size():
ifREDIS_AVAILABLEandredis_client:
try:
return {"queue_size": redis_client.llen("analytics:worker_queue"), "queue_name": "analytics:worker_queue"}
exceptException:
return {"queue_size": -1, "error": "redis_unavailable"}
return {"queue_size": -1, "error": "redis_not_configured"}Requirements (analytics-service/requirements.txt):
fastapi==0.115.0
uvicorn[standard]==0.30.0
redis==5.1.0
"""Galleri5 Analytics WorkerBackground worker that processes events from Redis queue."""importosimporttimeimportjsonimportsignalimportlogginglogging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger=logging.getLogger("analytics-worker")
REDIS_HOST=os.getenv("REDIS_HOST", "redis")
REDIS_PORT=int(os.getenv("REDIS_PORT", "6379"))
QUEUE_NAME="analytics:worker_queue"POLL_INTERVAL=int(os.getenv("POLL_INTERVAL", "2"))
shutdown=Falsedefhandle_signal(signum, frame):
globalshutdownlogger.info(f"Received signal {signum}, shutting down gracefully...")
shutdown=Truesignal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
defprocess_event(event: dict):
event_type=event.get("event_type", "unknown")
data=event.get("data", {})
logger.info(f"Processing event: {event_type}")
ifevent_type=="creator_added":
name=data.get("name", "unknown")
followers=data.get("followers", 0)
engagement=data.get("engagement_rate", 0)
score= (followers*engagement) /100logger.info(f"Computed score for {name}: {score:.0f}")
time.sleep(1)
elifevent_type=="campaign_created":
client=data.get("client", "unknown")
budget=data.get("budget", 0)
creator_count=len(data.get("creators", []))
logger.info(f"Campaign for {client}: budget {budget}, {creator_count} creators")
time.sleep(2)
else:
logger.warning(f"Unknown event type: {event_type}")
time.sleep(0.5)
try:
redis_client.incr(f"worker:processed:{event_type}")
redis_client.incr("worker:processed:total")
exceptExceptionase:
logger.warning(f"Failed to update processed count: {e}")
defmain():
globalredis_clientimportredisasredis_libredis_client=redis_lib.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, socket_timeout=5)
retries=0whileretries<30:
try:
redis_client.ping()
logger.info("Connected to Redis")
breakexceptException:
retries+=1logger.info(f"Waiting for Redis... (attempt {retries}/30)")
time.sleep(2)
else:
logger.error("Could not connect to Redis after 30 attempts")
returnlogger.info(f"Worker started. Polling queue: {QUEUE_NAME}")
whilenotshutdown:
try:
result=redis_client.brpop(QUEUE_NAME, timeout=5)
ifresult:
_, raw=resultevent=json.loads(raw)
process_event(event)
else:
logger.debug("Queue empty, waiting...")
exceptjson.JSONDecodeErrorase:
logger.error(f"Invalid JSON in queue: {e}")
exceptExceptionase:
logger.error(f"Worker error: {e}")
time.sleep(POLL_INTERVAL)
logger.info("Worker shutdown complete")
if__name__=="__main__":
main()Requirements (worker/requirements.txt):
redis==5.1.0
This is a dev-quality compose file left by a developer. It works... sometimes. It needs production hardening.
version: "3.8"services:
creator-api:
build: ./creator-apiports:
- "8000:8000"environment:
- ENVIRONMENT=development
- MONGODB_URI=mongodb://mongo:27017
- REDIS_HOST=redis
- ANALYTICS_SERVICE_URL=http://analytics-service:8001
- API_SECRET=supersecretkey123depends_on:
- mongo
- redisanalytics-service:
build: ./analytics-serviceports:
- "8001:8001"environment:
- ENVIRONMENT=development
- REDIS_HOST=redis
- API_SECRET=supersecretkey123worker:
build: ./workerenvironment:
- REDIS_HOST=redismongo:
image: mongo:7ports:
- "27017:27017"# no auth, no volume — data lost on restartredis:
image: redis:7ports:
- "6379:6379"# no password, no persistence# nginx not configured — services exposed directlyEach service has the same basic Dockerfile. These need production hardening.
# creator-api/Dockerfile and analytics-service/DockerfileFROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# Note: analytics-service should use port 8001# worker/DockerfileFROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "main.py"]#!/bin/bashset -e
BASE_URL="${1:-http://localhost:8000}"
ANALYTICS_URL="${2:-http://localhost:8001}"
PASS=0
FAIL=0
check() {
local desc="$1"local url="$2"local expected_code="${3:-200}"
response=$(curl -s -o /dev/null -w "%{http_code}""$url"2>/dev/null ||echo"000")if [ "$response"="$expected_code" ];thenecho"PASS: $desc (HTTP $response)"
PASS=$((PASS +1))elseecho"FAIL: $desc (expected $expected_code, got $response)"
FAIL=$((FAIL +1))fi
}
echo"=========================================="echo" Galleri5 Platform Smoke Test"echo"=========================================="echo""echo"--- Creator API ---"
check "Health check""$BASE_URL/health"
check "List creators""$BASE_URL/creators"
check "Get creator by ID""$BASE_URL/creators/c001"
check "Filter by platform""$BASE_URL/creators?platform=instagram"
check "Invalid creator 404""$BASE_URL/creators/invalid" 404
check "List campaigns""$BASE_URL/campaigns"
check "Get campaign""$BASE_URL/campaigns/camp01"
check "Analytics summary""$BASE_URL/analytics/summary"echo""echo"--- Analytics Service ---"
check "Analytics health""$ANALYTICS_URL/health"
check "Analytics summary""$ANALYTICS_URL/summary"
check "Recent events""$ANALYTICS_URL/events/recent"
check "Queue size""$ANALYTICS_URL/queue/size"echo""echo"--- Create Operations ---"
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/creators" \ -H "Content-Type: application/json" \ -d '{"name":"Test Creator","platform":"instagram","followers":50000,"category":"fashion","engagement_rate":3.5}')if [ "$code"="200" ];thenecho"PASS: Create creator"; PASS=$((PASS+1));elseecho"FAIL: Create creator (got $code)"; FAIL=$((FAIL+1));fi
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/campaigns" \ -H "Content-Type: application/json" \ -d '{"client":"TestCorp","name":"Test Campaign","budget":100000,"creator_ids":["c001","c002"]}')if [ "$code"="200" ];thenecho"PASS: Create campaign"; PASS=$((PASS+1));elseecho"FAIL: Create campaign (got $code)"; FAIL=$((FAIL+1));fiecho""echo"Waiting 5s for worker to process..."
sleep 5
check "Analytics updated""$ANALYTICS_URL/summary"echo""echo"=========================================="echo" Results: $PASS passed, $FAIL failed"echo"=========================================="
[ "$FAIL"-gt 0 ] &&exit 1Your job is to take this from "developer's laptop" to "production-ready deployment."
Create the project structure with all the code above
Make the existing setup production-worthy:
- Dockerfiles: Optimize all 3 (multi-stage builds, small images, non-root users, proper layering)
- docker-compose.yml: Fix the security and reliability issues:
- MongoDB should have authentication and persistent volume
- Redis should have a password and persistence
- Secrets should NOT be hardcoded
- Services should have health checks, restart policies, and resource limits
- Add NGINX as reverse proxy in front of Creator API
- Networking: Only NGINX should be exposed to the host. Other services should be internal only.
Verify everything works:
bash scripts/test.sh
Write Terraform code to provision cloud infrastructure for this platform.
We prefer Azure (since we're migrating to Azure), but GCP or AWS is acceptable.
Infrastructure should include:
- Container runtime (AKS / Cloud Run / ECS — your choice, justify it)
- Container registry
- Managed Redis
- Secrets management
- Networking (VNet/VPC, subnets, security rules)
- Logging configuration
You don't need to terraform apply — we'll review the code quality and design decisions.
Write Kubernetes manifests to deploy all 3 services:
- Deployments with health checks, resource limits, and proper env config
- Services (ClusterIP for internal, Ingress for external)
- HPA for Creator API (scale on CPU)
- Secrets (not hardcoded)
- Worker scaling strategy (mention KEDA if you know it)
If you chose serverless (Cloud Run / ECS Fargate), write the equivalent config.
Create a GitHub Actions pipeline:
On Pull Request:
- Lint Python code
- Build all 3 images
- Run smoke test against docker-compose
On Push to main:
- Build and tag images (Git SHA, not
latest) - Push to container registry
- Deploy
At minimum:
- Structured logging across all services
- Health checks wired into infrastructure
- Basic monitoring setup
Bonus: metrics, alert rules, dashboards
Provide a SOLUTION.md explaining:
- Architecture overview (diagram preferred)
- Why you chose specific tools and configurations
- Security improvements you made
- How services communicate and what happens if one goes down
- How you would scale this for 10x traffic
- What you'd do differently with more time
- Estimated monthly cloud costs
- Time spent on each step
- Auto-scaling configuration (HPA, KEDA)
- Rollback strategy
- Network policies
- Rate limiting (NGINX or app level)
- Container security scanning in CI/CD (Trivy)
- Helm chart
- Load testing script
# Create the project structure from the code above
mkdir -p galleri5-devops-task/{creator-api,analytics-service,worker,scripts}
# Copy each service's code, requirements.txt, and Dockerfile# Copy docker-compose.yml and test.sh# Then:
docker compose up --build
bash scripts/test.sh
# Now make it better.Push a private GitHub repository containing:
- Fixed Dockerfiles (all 3 services)
- Fixed docker-compose.yml (with NGINX, secrets, volumes, health checks)
- NGINX configuration
- Terraform code (terraform/ directory)
- Kubernetes manifests or equivalent deployment config (k8s/ directory)
- CI/CD pipeline (.github/workflows/)
- Smoke test passing (scripts/test.sh should work with your setup)
- SOLUTION.md with your architecture decisions
- Monitoring setup (Prometheus/Grafana config)
- Helm chart
- Network policies
- Load testing script
- Architecture diagram
- Create a private GitHub repository
- Add sandeep.mannepalli@galleri5.com as a collaborator
- Share the repo link to sandeep.mannepalli@galleri5.com
If anything is unclear, email us (sandeep.mannepalli@galleri5.com)
Good luck!