The Pipeline That Nobody Wants to Touch
There is a particular kind of DevOps pipeline that most engineers eventually encounter. It works. Nobody really likes it. Nobody is entirely sure who originally wrote it.
And everyone knows that changing it before a Friday production deployment is probably a bad idea. Maybe it started as a simple Jenkinsfile.
Over the years, someone added a security scan. Someone else added a manual approval. Then came a workaround for an old deployment system. A shell script appeared because the Jenkins plugin couldn’t do something reliably. Another script was added because the first script had a problem.
Eventually, you end up with something like this:
stage('Deploy') {
steps {
sh './scripts/prepare.sh'
sh './scripts/deploy.sh'
sh './scripts/verify.sh'
}
}
And somewhere inside those scripts are another few hundred lines of Bash.
Then one day, someone gives the whole thing to an AI coding agent and asks:
“Can you modernize this CI/CD pipeline?”
And the AI comes back with a beautifully clean pipeline. That’s where things get interesting.
AI Sees Technical Debt. Engineers See History.
If I look at an old pipeline and see five shell scripts, duplicated environment variables, strange conditionals and a Jenkins plugin that hasn’t been updated in years, my first reaction might be the same as AI’s.
Why is this so complicated and why are we doing this manually?
Why aren’t we using the newer deployment mechanism? Why do we have three different scripts doing something that looks like it could be one command?
From a purely technical perspective, those are reasonable questions. But legacy pipelines have something that source code doesn’t always make obvious: history.
That strange shell command might exist because a previous version of the deployment tool had a bug. That environment variable might exist because one particular production server doesn’t behave like the others. That manual approval might have been introduced after an incident. That seemingly redundant verification step might be checking something that the deployment tool itself cannot detect. The pipeline may look messy because the system around it is messy. And AI doesn’t automatically know that history.
The Modernization Looks Great
Let’s say the original pipeline does something like this:
$ ./deploy.sh production
Checking environment...
Loading production variables...
Running database pre-check...
Deploying application...
Waiting for deployment...
Running smoke tests...
Deployment completed successfully.
An AI agent looks at this and suggests replacing several of those steps with a cleaner deployment stage.
Maybe:
$ kubectl apply -f deployment.yaml
deployment.apps/payments-api configured
$ kubectl rollout status deployment/payments-api
deployment "payments-api" successfully rolled out
Much cleaner. And technically, that might be exactly what we want. But here’s the question I would ask before merging it: What was deploy.sh actually doing?
Because the filename doesn’t tell us everything. Maybe it was doing much more than deploying the application. Maybe it was checking whether a database migration was required. Maybe it was updating an external load balancer. Maybe it was waiting for a legacy service to become available. Maybe it was deliberately running things in a specific order because the production environment required it.
The AI sees a script. The engineer needs to understand the behavior hidden inside the script.
Legacy Pipelines Often Contain Invisible Contracts
This is where legacy DevOps systems get particularly difficult. A pipeline can have dependencies that aren’t obvious from the pipeline itself.
For eg:
$ grep -R "production" Jenkinsfile scripts/
Jenkinsfile: environment = "production"
scripts/deploy.sh: source /opt/company/env/production.env
scripts/verify.sh: curl https://internal-api/health
scripts/rollback.sh: ./legacy-rollback production
The pipeline may depend on files, servers, credentials, APIs, naming conventions and operational procedures that exist outside Git.
That’s an invisible contract and AI can only reason about what it can actually see. If you give an AI agent the Jenkinsfile but not the scripts it calls, it might confidently recommend removing something that looks unnecessary. If you give it the repository but not the operational documentation, it may miss why a particular step exists.
If you give it the code but not the history of production incidents, it has no way of knowing that a strange-looking workaround may actually be protecting the system. This is where context becomes more important than code quality.
The Most Dangerous Changes May Be the Cleanest Ones
This is probably the part of legacy modernization that I find most interesting. When AI improves a piece of code, the result often looks better.
The pipeline becomes shorter and configuration becomes more consistent. The number of steps goes down and scripts disappear. The deployment process becomes easier to understand and that can make the change look safer.
But reducing complexity in the pipeline doesn’t necessarily reduce complexity in the system. Sometimes you’ve simply moved the complexity somewhere else. For example, imagine the old pipeline has:
./precheck.sh
./deploy.sh
./verify.sh
And AI turns that into:
kubectl apply -f deployment.yaml
kubectl rollout status deployment/app
That is certainly cleaner. But what happened to everything that precheck.sh and verify.sh were doing? If those scripts contained important operational checks, we haven’t modernized the pipeline. We’ve removed safeguards, and that’s a very different thing.
This Doesn’t Mean AI Can’t Modernize Legacy Pipelines
I actually think this is one of the areas where AI can be extremely useful. Legacy pipelines are often difficult to understand because the knowledge is distributed across Jenkinsfiles, Bash scripts, documentation, configuration files and tribal knowledge.
AI is very good at helping us explore that complexity. For example, I can ask it:
“Trace everything that happens when this pipeline deploys to production.”
Or:
“Show me every external dependency used by this Jenkins pipeline.”
Or:
“Explain what this shell script is checking before deployment.”
That is a much safer starting point than:
“Rewrite this pipeline.”
The first approach asks AI to understand the existing system. The second asks it to replace the existing system. Those are very different tasks.
I Would Start With Read-Only AI
If I were introducing AI into an old production pipeline, I wouldn’t start by giving it permission to modify the pipeline.
I’d start by letting it observe. Give it the Jenkinsfile, the scripts, the deployment configuration, the logs and all the documentation. Then ask questions.
The pipeline isn’t just a collection of strange commands anymore. We can start asking why those commands appeared. That historical context can be incredibly valuable when deciding what is safe to remove.
Modernization Should Be Incremental
This is where I think AI can work particularly well with legacy DevOps.
Instead of asking:
“Replace this entire pipeline.”
I’d rather ask:
“Identify the parts of this pipeline that can be safely modernized, explain what each part currently does, and identify dependencies or risks before proposing changes.”
Then make one change, test it and observe it. Then make another change. Run it in a lower environment and Compare the behavior. Then gradually move toward the new pipeline.
The goal isn’t to make the Jenkinsfile beautiful. The goal is to change the system without accidentally removing knowledge that the old pipeline encoded.
AI Meets Legacy: Context Before Change
This is probably the bigger lesson I take from this. AI is very good at looking at a messy pipeline and saying:
“I can make this simpler.”
Sometimes that’s exactly what we need. But before simplifying a legacy DevOps system, I want to understand why it became complicated in the first place.
Because technical debt isn’t always random. Sometimes it’s accumulated operational knowledge. A strange script might represent an old incident. A manual step might represent a control that was introduced after a failure. A redundant check might exist because something once went badly without it.
And if we remove those things without understanding them, we may end up with a pipeline that looks much better while being operationally worse. That’s the paradox of AI and legacy DevOps.
AI can see the mess very quickly.
The engineer still needs to understand the history behind the mess.
And that’s where I think AI can be most useful — not as the thing that immediately rewrites the legacy pipeline, but as the engineer’s assistant for understanding it before we decide what should change.
Note: This article was written with the assistance of AI tools for structuring and drafting. The ideas, examples, and perspectives are based on real-world experience in DevOps and cloud engineering.
Top comments (1)
Point well made - people should use AI more to analyze, to explain, and to explore options, rather than expecting that 'I can click a button and it just "does what I want and solves all my problems"' - I really think we should do more of the former with AI, and less of the latter ...