Containerising an IoT telemetry stack with Docker Compose and AWS ECR, building a CI/CD pipeline that automatically rebuilds and redeploys on every push โ stepping stone toward AWS IoT Core and MSK.
RoleCloud & DevOps Architect
PlatformAWS EC2 ยท ECR ยท IAM
DomainIndustrial IoT
TypeContainerisation & CI/CD
The Problem
An IoT stack that was impossible to deploy consistently
The IoT pipeline ran as bare processes on EC2 with manual start scripts. Every environment change meant SSH, stop, pull, restart โ with no consistency, rollback, or visibility into which version was running.
Goal: Containerise the entire stack, push to AWS ECR, and have GitHub Actions automatically build and redeploy on every merge to main โ with a clear path toward AWS IoT Core and MSK in Phase 2.
Architecture
ECR-backed Docker Compose with GitHub Actions CI/CD
1
Dockerise each serviceIndividual Dockerfiles for MQTT broker, Kafka+Zookeeper, Python data consumer, Node.js simulator, React dashboard. Each image tagged with Git SHA for traceability.
2
AWS ECR private registryOne ECR repo per service. IAM role on EC2 with ECR permissions. GitHub Actions runner uses OIDC federation โ no long-lived AWS keys stored in secrets.
3
GitHub Actions CI/CD pipelineOn push to main: build images โ push to ECR with :latest and :git-sha tags โ SSH to EC2 โ docker-compose pull โ docker-compose up -d.
4
docker-compose migration to ECR referencesReplaced build: . directives with ECR image URIs. Added pre-pull step authenticating ECR before compose runs.
5
DynamoDB for device state persistenceTelemetry snapshots in DynamoDB. No persistent volume required โ state survives container restarts via DynamoDB reads on startup.
Key Challenges
IAM, ECR auth, and container ordering
๐
ECR authentication expiring mid-pipelineECR tokens expire after 12 hours. Fixed by adding a fresh aws ecr get-login-password call at the start of every deploy step.
๐ฆ
Kafka not ready when consumer starteddepends_on only waits for container start, not service readiness. Fixed with a healthcheck on Kafka and condition: service_healthy.
๐
MQTT broker unreachable from simulatorSimulator used localhost:1883 hardcoded. Inside Docker, containers communicate via service names. Fixed by replacing localhost with the docker-compose service name.
Outcomes
Deployments went from 20-minute manual ops to a git push