CI/CD for Teams That Still Deploy Manually
Quick takeaways
- You don't need a perfect test suite to start. The first pipeline can simply build the code the same way every time. Tests get added as the pipeline earns trust.
- Automate the steps you already do, in the order you do them. Your current release checklist is the first draft of your pipeline.
- Build once, deploy the same artifact everywhere. If staging and production run different builds, staging proves nothing.
- Database changes are where automated deploys break. Make every migration safe to run while the old version of the code is still live.
- Your pipeline is now a production system. It holds the keys to your cloud, so it needs short-lived credentials and pinned dependencies.
- Measure it with DORA's five metrics, not with how many tools you installed.
If your team releases software by following a checklist in a wiki, logging into a server, and hoping nothing unexpected happens, you're not alone. Plenty of profitable companies still deploy that way. It works until the person who knows the steps is on vacation, or until a release goes wrong at 9 p.m. and nobody is sure how to undo it.
This guide is for those teams. It skips the theory and focuses on a practical CI/CD pipeline setup: what to automate first, what to leave for later, and the handful of problems (databases, secrets, rollbacks) that decide whether automation sticks.
What CI/CD actually means
CI/CD is three related practices that often get blurred together:
| Term | What it means | What it gives you |
|---|---|---|
| Continuous integration (CI) | Every change is merged into a shared branch often, and each merge is automatically built and tested | Problems surface within minutes of the change that caused them |
| Continuous delivery | Every change that passes the pipeline is ready to release, and releasing is a button press | Releases become a business decision, not an engineering event |
| Continuous deployment | Every change that passes the pipeline goes to production automatically, with no button | The fastest feedback, but it needs strong tests and monitoring |
Most teams moving off manual deploys should aim for continuous integration plus continuous delivery. A person still decides when production changes, but the "how" is fully automated. Continuous deployment is a later step, and for some regulated products it's never the right one.
What manual deploys really cost
The obvious cost is time: an evening of someone's attention for each release. The bigger costs are harder to see.
- Knowledge sits with one person. The release works because a specific engineer remembers the order of eleven steps and the one that must never be skipped. That's a single point of failure with a vacation schedule.
- Every environment drifts. When servers are changed by hand, staging and production slowly stop matching. A release that passed staging fails in production for reasons nobody can reproduce.
- Nobody can say exactly what's running. Which version is in production right now? Which commit? If the answer takes more than a few seconds, an incident will take hours.
- Undoing a release is improvised. Without a defined rollback, recovery means rebuilding the old version from memory while customers are affected.
- Audits get painful. Healthcare, fintech and enterprise customers increasingly ask who changed production and when. A manual process rarely leaves that trail.
Signs your team needs a pipeline
If three or more of these sound familiar, a pipeline will pay for itself quickly:
- Releases are scheduled for evenings or weekends "in case something breaks"
- Only one or two people are trusted to deploy
- The release steps live in a document, a chat thread or someone's head
- "It worked on my machine" is a regular sentence
- You've had a release fail because a step was skipped or done out of order
- You release less often than you'd like because each release feels risky
- You can't answer "what version is in production?" without logging into a server
The minimum pipeline worth building
You don't need dozens of stages. A first pipeline that does these five things well is better than an ambitious one that's always red:
| Stage | Runs when | What it does |
|---|---|---|
| 1. Build | Every push and pull request | Compiles or bundles the code the same way every time, on a clean machine |
| 2. Check | Every push and pull request | Runs linting and whatever automated tests exist, and blocks the merge if they fail |
| 3. Package | On merge to the main branch | Produces one versioned artifact, such as a container image or release bundle |
| 4. Deploy to staging | Automatically after packaging | Installs that exact artifact in a staging environment and runs a quick smoke test |
| 5. Promote to production | When a person approves | Deploys the same artifact to production, with a one-step way back to the previous version |
The important word in stages 4 and 5 is same. If production gets rebuilt from source instead of using the artifact that passed staging, you're testing one thing and shipping another.
Going from manual to automated in six steps
The safest migration doesn't replace your process. It automates it, one step at a time, while the manual path still exists as a fallback.
Step 1: Write down what you do today
Have the person who usually deploys do a release while someone else writes down every command, click and check, in order. Include the steps they do without thinking. That list is your pipeline specification, and it often reveals steps nobody else knew about.
Step 2: Turn the list into a script
Before any CI tool is involved, turn the written steps into a single script that lives in your repository. Run it manually for a few releases. If the script can deploy, the pipeline only has to run the script.
Step 3: Put configuration under version control
Environment settings, server setup and build configuration belong in the repository too, with secrets kept out and stored in a secrets manager. This is also the point where defining infrastructure as code starts paying off, because environments can be recreated rather than repaired.
Step 4: Run CI on every pull request
Connect the repository to a CI tool and make it build the code and run existing tests on every pull request. Then protect the main branch so nothing merges while the build is failing. This step alone changes team habits, because broken builds get noticed by the person who caused them.
Step 5: Automate staging
Have every merge produce a versioned artifact and deploy it to staging automatically. Containers are a common way to do this, because the image that passed staging is byte-for-byte the image that runs in production. Keep the manual production process for now, and compare the two.
Step 6: Automate production, with approval and rollback
Once staging deploys have been boring for a few weeks, point the same pipeline at production behind an approval step. Before the first automated production release, rehearse the rollback: deploy the previous version through the pipeline and confirm it works. A rollback you haven't tested is a hope, not a plan.
That's the path we followed in our cloud infrastructure migration case study, where manual runbooks were replaced with CI/CD pipelines and deployments moved from monthly maintenance windows to multiple releases a day, with zero downtime during the move.
Database changes: where automated deploys break
Code is easy to roll back. Data isn't. Most failed automated releases trace back to a database migration that assumed the old code would disappear the instant the new code arrived.
In reality, there's always a window where both versions run: during a rolling deploy, a staged rollout, or a rollback. The reliable pattern is to split every breaking change into steps that are each safe on their own, often called expand and contract:
- Expand. Add the new column or table without removing anything. Old code ignores it.
- Migrate. Deploy code that writes to both the old and new structure, then backfill existing data.
- Switch. Deploy code that reads from the new structure.
- Contract. In a later release, once nothing uses the old structure, remove it.
It's more releases than a single "rename the column" change, but each one can be rolled back without losing data. Two other rules help: run migrations as their own pipeline step so a failure stops the deploy before the code changes, and take a verified backup before any migration that deletes or rewrites data.
Securing the pipeline itself
Once a pipeline can deploy to production, it holds the keys to your production environment. Attackers know this. In March 2025, CISA warned that a popular third-party GitHub Action, tj-actions/changed-files, had been compromised, allowing disclosure of secrets including valid access keys from the pipelines that used it.
A few practices close most of that risk:
- Use short-lived credentials instead of stored keys. GitHub's security hardening guide recommends OpenID Connect so workflows can authenticate directly to the cloud provider and you can stop storing long-lived secrets. AWS, Azure and Google Cloud all support this.
- Pin third-party actions and plugins to a commit SHA, not a version tag. GitHub notes this helps mitigate the risk of a backdoor being added to the action's repository. Tags can be moved; a commit SHA can't.
- Give the pipeline only the permissions each job needs. A job that runs tests doesn't need permission to deploy.
- Protect production deploys. Require approval from a named group, and allow production credentials only on the protected main branch.
- Keep secrets out of logs. Never print environment variables while debugging, and check that your CI tool masks secret values.
Releasing safely: rollbacks, flags and staged rollouts
Automation makes releases consistent. These practices make them safe:
One-step rollback. Because every release is a versioned artifact, rolling back means redeploying the previous one. It should take one action and a few minutes, and it should be rehearsed.
Health checks after every deploy. The pipeline should confirm the new version is responding and error rates are normal before it reports success. If the checks fail, it rolls back on its own.
Feature flags. Ship code that's turned off, then turn it on for internal users, then for everyone. Deploying and releasing become separate decisions, and switching a flag off is faster than any rollback.
Staged rollouts. Send a new version to a small share of traffic first, watch it, then widen. The cost of skipping this was shown on July 19, 2024, when a faulty CrowdStrike content update crashed Windows systems worldwide. In its preliminary post-incident review, CrowdStrike committed to a staggered deployment strategy for those updates, starting with a canary deployment. Most teams don't ship to millions of machines, but the principle scales down: nothing should reach every user at once.
How to know it's working
The goal isn't to have a pipeline. It's to ship changes faster without breaking more things. Google's DORA research program measures that with five software delivery metrics, split between throughput and instability:
| Metric | What it measures | What it tells you |
|---|---|---|
| Change lead time | Time from a change being committed to it running in production | How long finished work waits |
| Deployment frequency | How often you deploy | Whether releasing has stopped being an event |
| Failed deployment recovery time | Time to recover from a deployment that needs immediate intervention | Whether your rollback actually works |
| Change fail rate | Share of deployments that need immediate intervention | Whether speed is costing quality |
| Deployment rework rate | Share of deployments that are unplanned and caused by a production incident | How much release time goes to fixing rather than building |
Write down your current numbers before you start, even rough ones. Three months later, they're the clearest evidence of whether the work paid off, and the best answer when someone asks why engineering spent time on the pipeline instead of features.
Mistakes that make teams give up on CI/CD
Ignoring flaky tests. A test that fails randomly teaches everyone to click "re-run" without reading the result. Fix it or quarantine it within a day, or the pipeline loses its authority.
A pipeline nobody wants to wait for. If checks take forty minutes, people batch changes to avoid them, which is the habit you were trying to break. Run the fast checks on every push and the slow ones on merge.
"Just this once" manual changes. One hand-edited server in production undoes the guarantee that environments match. If something must change, it changes through the pipeline.
Automating everything on day one. Teams that try to build the perfect pipeline in one go often spend months and ship nothing. Start with build and test, prove it, then add stages.
No owner. A pipeline is software. Someone needs to own it, keep it fast and update its dependencies, or it slowly decays back into a ritual nobody trusts.
Choosing a CI/CD tool
The tool matters less than people expect. A sensible default is to use the CI/CD built into wherever your code already lives:
- GitHub Actions if your code is on GitHub
- GitLab CI/CD if you use GitLab
- Azure Pipelines for teams already standardized on Azure DevOps
- Your cloud provider's native tools, such as AWS CodePipeline or Google Cloud Build, when you want everything inside one cloud account
All of them can run the five-stage pipeline above. The questions that should decide it are practical: where your code is hosted, which cloud you deploy to, whether you need runners inside a private network, and who on your team will maintain it.
Once releases are automated, the next question is usually what they cost to run. Our guide on why your cloud bill keeps growing covers that, including how infrastructure as code keeps savings from drifting back.
Frequently asked questions
What's the difference between continuous delivery and continuous deployment?
With continuous delivery, every change that passes the pipeline is ready to release, but a person decides when it goes to production. With continuous deployment, every passing change goes to production automatically. Most teams moving off manual releases should start with continuous delivery and consider continuous deployment only once their tests and monitoring are strong.
Do we need automated tests before we can set up CI/CD?
No. A first pipeline can simply build the code the same way every time and deploy a consistent artifact, which already removes most manual errors. Add a few tests for the most important flows, such as sign-in and checkout, then expand coverage over time. Waiting for a complete test suite usually means never starting.
Can we use CI/CD without Docker or Kubernetes?
Yes. CI/CD only requires a repeatable way to build, test and deploy. Containers make that easier because the same image runs everywhere, but a pipeline can just as well deploy a versioned bundle to virtual machines or a managed platform. Kubernetes is worth adding only when your workload needs it, not as a prerequisite for automation.
How do we handle database changes in an automated pipeline?
Make every migration safe to run while the previous version of the code is still live. Split breaking changes into steps: add the new structure, write to both, switch reads, then remove the old structure in a later release. Run migrations as a separate pipeline step, and back up data before any change that deletes or rewrites it.
Is it safe to deploy to production automatically?
It's usually safer than deploying by hand, provided the pipeline has three things: an approval step for production, health checks that confirm the new version is working, and a tested one-step rollback. Staged rollouts and feature flags add another layer by limiting how many users see a change before it's proven.
How do we keep secrets and cloud credentials safe in a CI/CD pipeline?
Use short-lived credentials through OpenID Connect instead of storing long-lived cloud keys, give each job only the permissions it needs, and restrict production credentials to the protected main branch. Pin third-party actions to a commit SHA rather than a tag, and make sure secret values are masked in logs.
Which CI/CD metrics should we track?
Google's DORA program uses five: change lead time, deployment frequency, failed deployment recovery time, change fail rate and deployment rework rate. Together they show whether you're shipping faster without breaking more. Record rough numbers before you start so you can show the improvement later.
Turn your release checklist into a pipeline
If your releases depend on one person, an evening and a wiki page, we'll help you automate them step by step, with the manual path kept as a fallback until the new one has proven itself.
DevOps and cloud engineering — or send us your current release checklist and we'll show you what we'd automate first.
