DevOps is a way of building and running software where the people who write the code and the people who run it share one set of tools, one process and one automated pipeline. The result is software that reaches users sooner and breaks less often when it gets there.
Why 'works on my machine' happens
Code never runs on its own. It depends on a runtime version, a pile of libraries, operating system packages and configuration: environment variables, connection strings, feature flags, file paths. Your laptop and the production server drift apart on every one of those. You installed Node 22 last month, the server still has Node 18. A library resolved to a newer minor version on your machine. An API key exists only in your shell profile.
Each difference is small. Together they mean the thing you tested is not the thing that runs. The code is the same, but the environment around it is not, so it crashes the moment it lands.
The fix is not 'be more careful'. It is to make environments reproducible, and to check every change on a machine that is not your laptop before any user sees it. Most of DevOps is about doing exactly that, automatically, every time.
What DevOps changes
In the older model, a development team wrote features and handed a release to a separate operations team, who deployed it and kept it running. The two teams wanted different things: developers were rewarded for change, operations for stability. Releases were bundled into large, infrequent drops, often after a long manual test phase.
That model has predictable problems:
- Bugs appear late. A change written in March might not meet a real server until June, when nobody remembers why it was written.
- Big releases are hard to debug. When fifty changes ship at once and something breaks, finding the one responsible is slow.
- Releases are slow and risky, so teams do them less often, which makes each one bigger and riskier still.
DevOps breaks that loop by having everyone work from the same material:
- Shared tools: one repository, one pipeline definition, one set of dashboards that developers and operations both read.
- Shared process: every change, however small, goes to production the same way. No special manual route for 'urgent' fixes.
- One pipeline: an automated path from a commit to a running release, which checks the change at every step.
Someone usually builds and looks after that pipeline and the platform under it, and that person is often called a DevOps engineer. But DevOps itself is not a job title or a product. If developers still throw code over a wall and walk away, renaming the operations team changes nothing.
The CI/CD pipeline
Two ideas sit at the centre of the pipeline:
- Continuous integration (CI): developers merge small changes into a shared branch often, at least daily, and every merge is built and tested automatically.
- Continuous delivery or deployment (CD): every change that passes the checks is ready to release at the press of a button (delivery), or is released automatically with no button at all (deployment).
Merging small changes often is much easier with short-lived branches; Branching Strategies compares the common approaches.
A typical pipeline runs these stages in order:
- Commit. Pushing code triggers the pipeline.
- Build. A clean machine installs dependencies from the lock file and compiles the code. Because it is not your laptop, missing dependencies and hidden local settings show up here instead of in production.
- Test. Unit tests check small pieces in isolation; integration tests check that the pieces work together.
- Secure. Scanners look for dependencies with known vulnerabilities, secrets committed by mistake and risky code patterns.
- Package. The pipeline builds one deployable artefact, often a container image, labelled with the commit it came from.
- Deploy. That exact artefact goes to a staging environment, then to production.
The rule that makes it work: if any step fails, the pipeline stops. A change that fails its tests never gets packaged, and one that fails the security scan never gets deployed. If every check passes, it ships. Nothing half-checked moves forward.
A worked example
Here is a small online shop written in Node, with its pipeline defined as a GitHub Actions workflow. Every push to main runs it:
name: ci
on:
push:
branches: [main]
jobs:
ship:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install
run: npm ci
- name: Test
run: npm test
- name: Audit dependencies
run: npm audit --audit-level=high
- name: Build image
run: docker build -t shop:${{ github.sha }} .
- name: Deploy to staging
run: ./deploy.sh staging ${{ github.sha }}Each step runs only if the one before it succeeded. npm ci installs exactly what the lock file says, not whatever is newest. If a test fails, the job ends at the test step and the deploy never runs. The image is tagged with the commit's SHA, so you always know which code is running where. deploy.sh stands in for whatever your hosting platform uses to release a new version.
Build once, deploy many times
Build the artefact once and promote the same one through staging and production. Rebuilding it for each environment quietly brings back the drift you were trying to remove: a library could resolve differently between two builds, and what you tested in staging is no longer what runs in production.
Keeping environments the same
The pipeline catches problems, but the deeper fix for 'works on my machine' is to stop environments differing in the first place.
Docker packages an application together with its runtime and libraries into a container image. The same image runs on your laptop, on the CI machine, in staging and in production, so the environment travels with the code:
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]The Node version is pinned in the first line, and dependencies come from the lock file. Configuration that differs between environments, such as a database address, stays out of the image and is passed in as environment variables, so staging and production run the identical image with different settings. Smaller images start faster and carry less to attack; Distroless Images takes that idea further.
Kubernetes runs containers across many machines. You describe what you want (three copies of the shop image, version abc123) and it works to keep reality matching: it restarts containers that crash, replaces machines that fail and rolls out a new version gradually. Not every team needs it. A small app on a single server or a managed hosting platform gets the same benefit from containers without the extra complexity.
Most teams keep at least three environments: local for development, staging as a rehearsal that mirrors production as closely as possible, and production for real users. The closer staging is to production, the fewer surprises the last step holds.
After the deploy: monitoring, logs and rollbacks
Shipping is not the end of the job. Once a release is live, the team needs to know whether it is healthy.
- Monitoring tracks numbers over time: error rate, response time, memory and CPU. Alerts fire when a number crosses a line, ideally before users notice.
- Logs record what the application did and why it failed. When an alert fires, logs are where you find the cause.
- Rollbacks undo a bad release. Because every release is a tagged artefact, rolling back means redeploying the previous tag, which takes minutes rather than a frantic late-night fix.
Small, frequent releases make all three easier. When only one change went out, a spike in errors points straight at it, and rolling it back loses very little. What the team learns in production then feeds into the next change, which is the loop DevOps is built around.
Common mistakes
- Buying a tool and calling it DevOps. A pipeline product without shared ownership still leaves developers and operations blaming each other.
- Bypassing the pipeline. A quick manual fix on the server makes it differ from what the pipeline deploys, and the next release quietly undoes the fix.
- Ignoring flaky tests. If people rerun a failing pipeline until it goes green, a stopped pipeline stops meaning anything.
- Secrets in the repository or baked into images. Pass them in at deploy time from a secrets store.
- Reaching for Kubernetes too early. It solves problems of scale; on a small app it mostly adds work.
Key takeaways
- 'Works on my machine' comes from environments that differ in configuration and dependencies.
- DevOps is a way of working: shared tools, shared process and one pipeline, not a product or a job title.
- A CI/CD pipeline builds, tests, scans, packages and deploys every change, and stops at the first failure.
- Containers keep local, staging and production the same; build the artefact once and promote it.
- Monitoring, logs and easy rollbacks make small, frequent releases safe.