Reading Time: 2 minutes

Note: CoCreated with Gpt 4o

This release wasn’t about shipping features — it was about making sure we can ship features faster and with more confidence going forward.

After running everything on a VPS with Docker Compose for years, I’ve migrated the backend of my project (SkillChamber + LingoStand) to Azure Cloud, using Azure Container Apps and Azure Flexible Server for MySQL.

Here’s a breakdown of the stack, what I learned, what broke, and what I set up for future scaling.


🧭 Architecture Overview

The backend is split into two services:

  • Python (Flask) for AI/NLP handling
  • Spring Boot for persistence, profile logic, routing, and frontend APIs

Both services now run in separate containers on Azure Container Apps, managed via Azure Container Registry (ACR).

App structure:
  - spring-backend: 2 vCPU, 4 GB RAM
  - python-backend: 1.25 vCPU, 2.5 GB RAM
  - ingress: enabled for both

No custom domain yet — just raw endpoints behind ACA ingress.


🛠️ CI/CD with GitHub Actions

I’ve created per-service GitHub Actions workflows, each responsible for:

  1. Building a Docker image
  2. Pushing it to Azure Container Registry
  3. Deploying the updated image to Azure Container Apps using az containerapp update

Secrets like ACR credentials and image tags are managed through GitHub Secrets. I did not use ARM templates or Bicep files — the setup was handled via Azure CLI and the portal.

Here’s a simplified example of one of the deploy steps:

- name: Deploy to Azure Container App
  run: |
    az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} \
             -p ${{ secrets.AZURE_CLIENT_SECRET }} \
             --tenant ${{ secrets.AZURE_TENANT_ID }}

    az containerapp update \
      --name python-backend \
      --resource-group my-resource-group \
      --image myregistry.azurecr.io/python-backend:latest

🐬 MySQL Migration (Painful Lessons)

The move to Azure MySQL Flexible Server came with a fair share of headaches. The biggest blocker?

UTF-8 character set incompatibility between my local MySQL and the default Azure Flexible config.

I’ve documented the fix in the README — it boils down to setting:

character_set_server = utf8mb4
collation_server = utf8mb4_unicode_ci

Once migrated, I set up automatic daily backups (default policy in Flexible Server).


📈 Observability Setup

I was pleasantly surprised by how frictionless observability was on Azure compared to other clouds I’ve used (AWS, GCP).

  • Logs: Azure Monitor
  • Metrics & autoscaling: via Container Apps resource metrics
  • Custom Dashboard: I created one that tracks replica count, CPU usage, and HTTP error rates

This gives me quick visibility into how the services are behaving under load — and it’s all native to the Azure portal.


🔐 Secrets and Environment

All sensitive credentials (MySQL, API keys, ACR access) are injected through GitHub Secrets during CI.

I no longer use Docker Compose locally — all environments now rely on cloud-native deployments.


🚧 What’s Next

This was a foundational release. Nothing flashy, but here’s what it enables:

  • 🛠️ Faster, safer deployments
  • 🌍 Scaling without fear
  • 🧪 More room to experiment with new agents and simulations

The next sprint will be back to features — now with a clean, observable, and scalable platform behind them.