I Automated My Workflow with DevOps Practices - Here's How I Did It
Look, I'll be honest with you – a year ago, I was that guy manually deploying code, crossing my fingers, and hoping nothing broke in production. I'd push changes, manually SSH into servers, run a few commands, and pray. It was chaotic, error-prone, and absolutely soul-crushing when something went wrong at 2 AM on a Sunday.
Then I realized: I'm literally a developer. Why am I doing manual work a script could handle?
So I decided to actually learn DevOps. Not the "I'm becoming a DevOps engineer" kind of learning, but rather adopting DevOps practices to make my own development life infinitely better. This isn't a story about becoming ops-savvy – it's about how automation changed everything for me, and I want to walk you through exactly what I did.
The Breaking Point
I was working on a mid-sized e-commerce platform with a small team. Every Friday, we'd have a release. And every Friday, I'd spend 2-3 hours doing repetitive tasks:
- Building the Docker image manually
- Pushing it to a registry
- Logging into AWS
- Updating the ECS task definition
- Restarting the services
- Running smoke tests
- Checking logs for errors
One Friday, I accidentally deployed the wrong image to production. Wrong. Image. The whole morning went away fixing it. That's when my brain finally clicked: This is stupid. Automate it.
Step 1: Containerize Everything (Docker)
First things first – I needed consistent environments. I started with Docker because, honestly, it's kind of become table stakes for developers now.
My Dockerfile went from non-existent to this:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
I'm not going to lie – my first few Dockerfiles were trash. I didn't understand layers, caching, or multi-stage builds. I just copied the entire project and called it a day. But you know what? I learned by doing.
The key thing here was realizing that Docker gave me reproducibility. What runs on my machine runs in production. Wild concept, I know.
Step 2: Set Up CI/CD with GitHub Actions
This is where the magic started happening. I didn't want to use Jenkins (seemed overkill) or pay for fancy CI/CD platforms. GitHub Actions is built right into GitHub and it's genuinely excellent.
Here's my workflow file that changed everything:
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build Docker image
run: docker build -t my-app:${{ github.sha }} .
- name: Push to ECR
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${{ secrets.ECR_REGISTRY }}
docker tag my-app:${{ github.sha }} ${{ secrets.ECR_REGISTRY }}/my-app:latest
docker push ${{ secrets.ECR_REGISTRY }}/my-app:latest
- name: Update ECS service
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws ecs update-service \
--cluster production-cluster \
--service my-app-service \
--force-new-deployment \
--region us-east-1
- name: Notify Slack
if: success()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-H 'Content-Type: application/json' \
-d '{"text":"Deployment to production successful! 🚀"}'
Yeah, that looks intimidating. I know because I stared at it confused for about 4 hours. But breaking it down:
-
When does it run? Every time I push to
main - What does it do? Check out code → Install deps → Run tests → Build Docker image → Push to ECR → Update ECS → Notify Slack
The beautiful part? Now I just git push, grab coffee, and the whole pipeline runs automatically. No manual intervention.
Step 3: Infrastructure as Code with Terraform
Next up was Terraform. My AWS infrastructure used to be a mess of clickety-clacks in the console. "Was that security group created by me or someone else?" Nobody knows.
Terraform let me define everything in code:
resource "aws_ecs_cluster" "main" {
name = "production-cluster"
}
resource "aws_ecs_task_definition" "app" {
family = "my-app"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
container_definitions = jsonencode([{
name = "my-app"
image = "${aws_ecr_repository.app.repository_url}:latest"
essential = true
portMappings = [{
containerPort = 3000
protocol = "tcp"
}]
}])
}
Now infrastructure changes go through git like code changes. Pull requests for infrastructure. Code review for your AWS setup. It's chef's kiss.
The learning curve here was real though. I definitely destroyed a production database once (didn't have protection enabled, my bad). But that's actually what made it valuable – I learned to respect infrastructure management.
Step 4: Monitoring and Alerting
Automating deployment is cool, but you know what's cooler? Knowing immediately when something breaks.
I set up Datadog (okay, slight budget on this one, but there are free alternatives like Prometheus) to monitor everything. Health checks, error rates, response times, database connections – all of it.
The beauty is that now I get alerted before users even notice something's wrong. I have a Slack integration that pings me with actionable information:
🚨 High Error Rate Detected
Service: my-app
Error Rate: 15% (threshold: 5%)
Duration: 5 minutes
Affected Endpoints: /api/checkout
Instead of waking up to angry customer emails, I wake up to a Slack notification, fix the issue, and redeploy. Takes me 20 minutes instead of 3 hours of chaos.
The Honest Truth
This didn't happen overnight, and it definitely wasn't friction-free. Here's what actually went down:
What Was Hard:
- Understanding GitHub Actions syntax (YAML is finicky, man)
- Getting AWS permissions right (IAM is a nightmare, not gonna sugarcoat it)
- First time I accidentally deleted the database with Terraform (yikes)
- Debugging why deployments were silently failing
What I Wish I'd Known Earlier:
- Start with GitHub Actions, not Jenkins
- Use managed services (ECS, RDS) instead of managing servers yourself
- Invest in monitoring from day one
- Terraform takes time to understand, but it's worth it
The Results
After six months of having this pipeline in place, here's what changed:
- Deployment time: 3 hours → 10 minutes (mostly waiting for tests to run)
- Failures in production: Multiple per month → Maybe one every few months
- Stress level: Genuinely lower. Deployments don't stress me anymore
- **Time
Disclosure: This article contains affiliate links. If you purchase through these links, I may earn a small commission at no extra cost to you.
📚 Want to learn more? Check out these top resources on Amazon.













