My GitHub Actions deploy workflow was 87 lines of YAML. It broke constantly, took 12 minutes, and I was the only one who understood it. Here's what I replaced it with.
Platform Super Admin
April 4, 2026

My GitHub Actions deploy workflow was 87 lines of YAML.
It had grown over 18 months from a clean 20-line file into something I was genuinely afraid to touch. It broke whenever a dependency updated. It had three hardcoded ARNs from an AWS account I was no longer using. It had a comment that said # TODO: fix this that had been there for 11 months.
Last month I deleted all 87 lines and replaced them with one command.
Here's exactly how I did it — and what I learned along the way.
This was my deploy workflow. See if any of this feels familiar:
1name: Deploy to Production
2
3on:
4 push:
5 branches: [main]
6
7jobs:
8 deploy:
9 runs-on: ubuntu-latest
10 steps:
11 - name: Checkout
12 uses: actions/checkout@v3
13
14 - name: Set up Node.js
15 uses: actions/setup-node@v3
16 with:
17 node-version: '20'
18 cache: 'npm'
19
20 - name: Install dependencies
21 run: npm ci
22
23 - name: Run tests
24 run: npm test
25
26 - name: Configure AWS credentials
27 uses: aws-actions/configure-aws-credentials@v2
28 with:
29 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
30 aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
31 aws-region: us-east-1
32
33 - name: Login to Amazon ECR
34 id: login-ecr
35 uses: aws-actions/amazon-ecr-login@v1
36
37 - name: Build Docker image
38 env:
39 ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
40 IMAGE_TAG: ${{ github.sha }}
41 run: |
42 docker build -t $ECR_REGISTRY/my-app:$IMAGE_TAG .
43 docker push $ECR_REGISTRY/my-app:$IMAGE_TAG
44 echo "IMAGE=$ECR_REGISTRY/my-app:$IMAGE_TAG" >> $GITHUB_ENV
45
46 - name: Download task definition
47 run: |
48 aws ecs describe-task-definition --task-definition my-app \
49 --query taskDefinition > task-definition.json
50
51 - name: Update ECS task definition
52 id: task-def
53 uses: aws-actions/amazon-ecs-render-task-definition@v1
54 with:
55 task-definition: task-definition.json
56 container-name: my-app
57 image: ${{ env.IMAGE }}
58
59 - name: Deploy to ECS
60 uses: aws-actions/amazon-ecs-deploy-task-definition@v1
61 with:
62 task-definition: ${{ steps.task-def.outputs.task-definition }}
63 service: my-app-service
64 cluster: my-app-cluster
65 wait-for-service-stability: true
66
67 - name: Notify on failure
68 if: failure()
69 run: |
70 curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
71 -H 'Content-type: application/json' \
72 --data '{"text":"Deploy failed! Check Actions."}'I wrote this. I'm not proud of it.
The real problem wasn't the YAML itself. The problem was everything hidden underneath the YAML:
Dockerfile I maintained separatelyThe pipeline was the visible part. The invisible part was 3 days of setup I did 18 months ago that I could no longer remember well enough to recreate.
When a new teammate joined and asked "how does deploy work?" — I sent them the workflow file and said "it's complicated."
That's not an answer. That's a warning sign.
In February, I switched from Node 18 to Node 20. The Docker build broke because my base image was pinned to node:18-alpine in three different places — the Dockerfile, the Actions workflow, and a .nvmrc file I had forgotten existed.
The fix took 45 minutes. The error message was not helpful. I fixed it by diffing my Dockerfile against a Stack Overflow answer from 2023.
Two weeks later, AWS deprecated the amazon-ecs-render-task-definition@v1 action. The pipeline broke silently — it ran, reported success, but the new image never actually deployed. I found out because a user filed a bug for something I had definitely already fixed.
That was the moment I decided: the pipeline is not worth maintaining.
I looked at Render and Railway. Both are good products. Neither deploys into my own AWS account — they provision their own infrastructure. My company has a compliance requirement that customer data stays in a customer-owned AWS environment. So those were out.
I looked at AWS CodePipeline. I wanted to solve complexity, not add more of it.
Then a colleague mentioned NEXUS AI. He described it as "a CLI that handles all the ECS stuff so you don't have to." I was skeptical. That's what everyone says.
I installed the CLI:
npm install -g @nexusai/cli
nexus loginThen I pointed it at my repo:
nexus deploy source \
--repo https://github.com/myorg/my-app \
--name my-app \
--provider aws_ecs_fargateI expected this to fail immediately. My expectations for new DevOps tools are calibrated by years of experience.
It didn't fail. Four and a half minutes later I got back a URL. The app was running. The same app. In my AWS account.
I checked the AWS console out of habit. There was an ECS cluster. A task definition. A service. An ECR repository with the image in it. NEXUS AI had provisioned all of it.
I had not written a Dockerfile. I had not configured any IAM roles. I had not touched the AWS console.
I sat with that for a moment.
Here's what nexus deploy source does, in order:
package.json, requirements.txt, go.mod, etc.Steps 3–5 are the 3 days of manual work I did 18 months ago. They now run in parallel and take about 3 minutes.
Here's my deploy workflow today:
1name: Deploy to Production
2
3on:
4 push:
5 branches: [main]
6
7jobs:
8 test:
9 runs-on: ubuntu-latest
10 steps:
11 - uses: actions/checkout@v4
12 - uses: actions/setup-node@v4
13 with:
14 node-version: '20'
15 cache: 'npm'
16 - run: npm ci
17 - run: npm test
18
19 deploy:
20 needs: test
21 runs-on: ubuntu-latest
22 steps:
23 - name: Deploy
24 run: nexus deploy redeploy --deployment-id ${{ secrets.NEXUSAI_DEPLOYMENT_ID }}
25 env:
26 NEXUSAI_TOKEN: ${{ secrets.NEXUSAI_TOKEN }}24 lines, including name: fields and blank lines.
The deploy job has one step. It calls nexus deploy redeploy, which tells NEXUS AI to rebuild from the latest commit and roll it out with a rolling update. No Docker commands. No AWS credentials. No ECR. No ECS task definition wrangling.
I kept the test job. NEXUS AI doesn't replace your test suite — it replaces everything after tests pass.
Before, I had secrets in three places: GitHub Actions secrets (for the pipeline), AWS Secrets Manager (for the app), and a .env.example file that was always slightly out of date.
Now:
nexus secret set \
DATABASE_URL=postgres://user:pass@host/db \
STRIPE_SECRET_KEY=sk_live_... \
NODE_ENV=productionThese are encrypted at rest and injected as environment variables when the container starts. The pipeline only needs NEXUSAI_TOKEN — one secret instead of seven.
After updating secrets, one command applies them:
nexus deploy redeploy --deployment-id <your-deployment-id>Old workflow rollback: figure out the previous image SHA, manually update the ECS task definition, trigger a new deployment, hope the old image hasn't been cleaned up by the ECR lifecycle policy.
New rollback:
nexus deploy rollback --deployment-id <your-deployment-id>Reverts to the previous container image. Health checks run. Done. I've used this twice. Both times took under 90 seconds.
First deploy: ~4.5 minutes (infrastructure provisioning included).
Subsequent deploys: 60–90 seconds. Infrastructure is already provisioned, so it's just build → push → rolling update.
My old pipeline took 10–12 minutes. Most of that was the Docker build running on GitHub's shared runners plus the ECS service stability wait.
Every tool has trade-offs. These are the real ones:
Less visibility into the build environment. With a Dockerfile I wrote, I knew exactly what was in the image. With source-based deployment, NEXUS AI generates the image. You can inspect it — nexus deploy logs gives the full build output — but you're not authoring the Dockerfile. For most apps this is fine. If you have specific system dependencies (custom C extensions, obscure shared libraries), test carefully.
The first deploy takes time. Infrastructure provisioning isn't instant. If you need sub-30-second cold deploys for some reason, this isn't that. But once infrastructure exists, redeployments are fast.
You're adding a dependency. NEXUS AI is now in your deploy path. Worth knowing.
| Old workflow | New workflow | |
|---|---|---|
| Lines of YAML | 87 | 24 |
| Pipeline runtime | 10–12 min | 60–90 sec |
| AWS console setup | ~3 days (one-time) | 0 |
| Secrets locations | 3 | 1 |
| Rollback steps | ~6 manual steps | 1 command |
| Last random breakage | November | Hasn't happened |
1# Install
2npm install -g @nexusai/cli
3
4# Authenticate
5nexus login
6
7# First deploy — detects Node/Python/Go automatically, no Dockerfile needed
8nexus deploy source \
9 --repo https://github.com/your/repo \
10 --name my-app \
11 --provider aws_ecs_fargate # or gcp_cloud_run, azure_container_apps
12
13# Check status
14nexus deploy status --deployment-id <id>
15
16# Set environment variables
17nexus secret set KEY=value KEY2=value2
18
19# Redeploy (use this in CI)
20nexus deploy redeploy --deployment-id <id>
21
22# Rollback
23nexus deploy rollback --deployment-id <id>For CI/CD: add NEXUSAI_TOKEN and NEXUSAI_DEPLOYMENT_ID as secrets in your GitHub repo settings, then replace your deploy steps with the one-liner from the workflow above.
The 87-line YAML file wasn't the real cost. The real cost was the cognitive overhead of owning it — the 45-minute debugging session when Node versions drifted, the silent failure when an Action was deprecated, the "it's complicated" I sent to a new teammate.
I don't miss any of that.
If you're maintaining a pipeline like the one I had, it's worth spending 20 minutes to find out how much of it you can delete.
Building something and want to compare notes? Drop it in the comments.
Log in to leave a comment.
Get started today
Deploy your first app in minutes. No DevOps team required. Full AI observability from day one.
No credit card required · Free tier available