HostiServer
2026-08-03 15:38
What CI/CD is: from manual deploy to automated pipelines
📚 "CI/CD from scratch" series, part 1 of 5:
- What CI/CD is: from manual deploy to automated pipelines ← you are here
- Self-hosted runner: architecture, installation, isolation
- The first pipeline in practice: GitHub Actions and GitLab CI
- Deploy to your own server: SSH, Docker registry, rollback
- CI/CD security and scaling: secrets, ephemeral runners, queues
What CI/CD is: from manual deploy to automated pipelines
Almost every project starts the same way. One developer, one server, one way to ship changes: connect over SSH, do git pull, restart the service. It works. While the project is small this is even faster than any automation, because the whole process takes a minute and requires no setup at all.
Problems begin not because of the size of the code, but because of the number of people and the frequency of changes. Two developers can already ship different versions of the same file to the server. Three already need an agreement on who deploys and when. Six months later a staging environment appears in the project, a year after that — a separate server for the client demo version, and every new server is one more set of manual steps that someone has to remember and perform without mistakes.
CI/CD is the answer to this problem. Instead of an instruction kept in someone's head (or in a deploy.txt file that nobody has updated for two years) there appears code that performs the same actions identically, every time and without human involvement. This article explains what exactly hides behind the abbreviation, what a pipeline consists of and which tools exist for this in 2026.
1.1 A typical situation without CI/CD
It looks roughly like this. The developer finished a feature, checked it locally, made a commit and a push. Then opens the terminal:
ssh deploy@192.0.2.10
cd /var/www/app
git pull origin main
npm ci --production
npm run build
sudo systemctl restart app
Six commands, two minutes. It looks safe exactly as long as everything goes according to the script. In reality a dozen assumptions hide between these lines: that the server has the same Node.js version, that git pull will not fail because of local changes someone left during a night-time incident, that the build will not eat all the memory and take down the neighbouring service, that after the restart the application really came up and did not fall into a crash loop.
The database is a separate story. In a manual process migrations are almost always run by a separate command that is easy to forget or to run twice. A classic of the genre: the code already requires a new column, but the migration has not been applied yet, and the application fails on every request during those five minutes while someone searches the chat for the right command.
1.2 What goes wrong
Manual deploy breaks along three directions, and all three only get worse over time.
The human factor. This is not about carelessness, but about statistics. Any sequence of ten steps that a person performs manually several times a week will sooner or later be performed with a mistake: the wrong branch, the wrong server in the second terminal tab, a forgotten step of building the frontend, a migration applied to the production database instead of staging. The more experienced the engineer, the less often this happens, but it never disappears completely.
Unsynchronised environments. The developer's local machine, staging and production almost always differ: in the interpreter version, in the version of system libraries, in the set of environment variables, in the presence of a cache. The phrase "it works on my machine" is not an excuse but an accurate description of a situation where the code really works in one environment and does not work in another. Without automation nobody knows how far exactly these three environments have drifted apart, because nobody has ever compared them systematically.
Fear of deploy. This is the most expensive consequence and the least noticeable one. If shipping changes is risky and is done manually, the team starts shipping less often. Instead of ten small changes a week there appears one big release once every two weeks. It contains hundreds of changed files, and when something breaks after it, nobody can quickly say which commit is to blame. The risk of a release grows, so releases are made even less often, and the cycle closes. The Friday "let's do it on Monday" is a symptom of exactly this problem.
ℹ️ Related article: if your project lives on your own hardware, the material "Out-of-band server management: IPMI, iDRAC, iLO and Redfish API" will come in handy. Deployment automation and hardware management automation are two parts of one task: to make the infrastructure manageable programmatically, not by hand.
2. CI and CD: what the difference is
The abbreviation CI/CD combines three different practices, and the confusion around it arises because the letter D means two different concepts at once. Let us take them apart separately.
2.1 CI — Continuous Integration
Continuous Integration is a practice in which every developer's code is automatically built and checked on every push to the repository. The key word here is integration: the idea is to merge changes into the shared branch often (daily or more often) and to check immediately that the shared code remains working.
A typical CI process is triggered on every push and on every pull request, and inside it does roughly the following:
- Builds the project. Compilation, installation of dependencies, frontend build, building a Docker image. If the build fails, there is no sense in going further.
- Runs linters and static analysis. Formatting, unused variables, potential type errors, code style violations.
- Runs the tests. Unit tests almost always, integration ones when available, sometimes e2e tests in a browser.
- Checks dependencies for vulnerabilities.
npm audit,pip-audit, Trivy for images, scanning for secrets in the code.
The result of CI is a yes/no answer to the question "can these changes be merged". In GitHub and GitLab this answer turns into a green or red mark next to the commit and into blocking the merge of the pull request until the checks pass.
An important detail: CI does not deploy anything anywhere. Its area of responsibility ends where the code has been recognised as fit.
2.2 CD — Continuous Delivery
Continuous Delivery is a continuation of CI: after successful checks the system automatically prepares the release and brings it to the state of "can be shipped at any moment". In practice this means:
- the built artifact (a Docker image, a
.debpackage, an archive with static files) is put into storage and marked with a version; - the artifact is automatically deployed to the staging environment;
- integration and acceptance tests are run on staging;
- the "ship to production" button is available, but it is a human who presses it.
The manual step here is not a shortcoming but a deliberate decision. It is needed where shipping has to coincide with business events (a marketing campaign, an agreed maintenance window, a mobile application release in the store) or where regulatory requirements assume confirmation from a responsible person.
2.3 CD — Continuous Deployment
Continuous Deployment removes the last manual step. Every change that has passed all the checks automatically reaches production without human involvement. A human presses the button exactly once: when merging the pull request.
This is the most mature level, and it requires infrastructure that will notice a problem instead of a human:
- Reliable test coverage. If the tests do not catch regressions, automatic deploy simply delivers broken code to clients faster.
- Feature flags. An unfinished feature goes to production switched off and is switched on separately, already without a deploy.
- Gradual rollout. Canary or blue-green: the new version first receives a part of the traffic, and only then all of it.
- Monitoring and automatic rollback. If after shipping the share of errors or the response latency has grown, the system returns the previous version by itself.
⚠️ A common mistake: Continuous Deployment is not "the same thing, only without the button". Removing the manual step is technically not difficult, it is one line in the config. Everything else in it is the difficult part: tests that can be trusted, observability, a fast rollback mechanism. A team that switched off the manual step without having built this gets not faster delivery but faster outages.
2.4 The scheme: code → CI → CD → production
The chain from a commit to production looks like this:
push to a branch
↓
[CI] build → lint → test → security scan
↓ (everything green)
[CD] artifact build → storage → deploy to staging → tests on staging
↓
Continuous Delivery: waits for manual confirmation
Continuous Deployment: goes further on its own
↓
production + monitoring + the possibility of rollback
The difference between the three practices comes down to where the automation ends:
| Practice | What is automated | The last step is done by | What you need to have |
|---|---|---|---|
| Continuous Integration | Build, linters, tests on every push | A human (merge and deploy) | Repository, tests, runner |
| Continuous Delivery | CI + artifact + deploy to staging | A human (the production button) | Artifact storage, staging |
| Continuous Deployment | Everything, including production | Automation | Test coverage, feature flags, monitoring, rollback |
A practical piece of advice: move down this table from top to bottom and do not skip rows. CI gives benefit immediately and almost without risk. Continuous Delivery requires several weeks to set up staging and storage. Continuous Deployment makes sense when the team has already been living with Delivery for several months and sees that the manual step has become a formality.
3. What happens inside a pipeline
A pipeline is a description of the whole process in the form of a file in the repository: .github/workflows/ci.yml for GitHub Actions, .gitlab-ci.yml for GitLab CI. The terminology of different systems differs in details, but the building blocks are the same everywhere: trigger, stage, job, artifact.
3.1 Trigger: the event that starts the pipeline
The trigger answers the question "when". The most common options:
- push to a certain branch. The main trigger for CI.
- pull request / merge request. Checks the result of the merge before it has happened.
- tag. Creating the tag
v1.4.0starts the release: artifact build, publication, deploy. - cron / schedule. Nightly runs of heavy tests, daily scanning of dependencies for new CVEs.
- manual run.
workflow_dispatchin GitHub, the Run pipeline button in GitLab. It is exactly this trigger that implements the manual step in Continuous Delivery. - an external event. A call through the API or a webhook: for example, a pipeline in a neighbouring repository that has built a new version of a library.
Separately, it is worth knowing about the path filter. In a monorepository there is no sense in running the backend tests when only a documentation file has changed:
# GitHub Actions
on:
push:
branches: [main]
paths:
- 'backend/**'
- '!**.md'
3.2 Stage: a logical group of steps
A stage answers the question "in what order". The classic set is build → test → deploy, and the point of the division is that the next stage starts only when the previous one has fully finished successfully. There is no sense in deploying what did not build, and in testing what did not build either.
Stages give one more useful property: everything inside one stage can be executed in parallel. If the test stage has unit tests, a linter and a vulnerability scanner, they are started simultaneously on different runners, and the stage lasts as long as the slowest of the three checks, and not as long as their sum.
3.3 Job: a specific task
A job is what is really executed: a set of commands in a specific environment on a specific runner. Every job starts in a clean environment, so it always begins with getting the code and installing the dependencies.
A minimal working example for GitHub Actions:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm test
The same meaning in the syntax of GitLab CI:
stages:
- build
- test
- deploy
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
test:
stage: test
image: node:20
script:
- npm test
deploy:
stage: deploy
script:
- ./scripts/deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
In GitHub Actions there are no stages as a separate entity, the order is set by dependencies through the needs key. In GitLab needs also exists and works on top of stages, allowing you to build a dependency graph instead of a strict queue. On large pipelines this noticeably shortens the total time.
3.4 Artifact: a file passed between jobs
Every job works in its own clean environment, so the result of one job's work is not visible to another automatically. Artifacts are the mechanism of passing: the build stage puts the ready files into storage, the deploy stage takes them from there.
Typical artifacts: the built frontend, a compiled binary, a generated package, and also reports (test results, code coverage, a vulnerability scanner report) that are convenient to look at right in the CI interface.
Artifacts are easy to confuse with a cache, although their tasks are different:
| Parameter | Artifact | Cache |
|---|---|---|
| Purpose | To pass the result further along the pipeline | To speed up repeated runs |
| Typical content | dist/, a binary, a package, a report |
node_modules/, ~/.m2, ~/.cache/pip |
| Can it be lost | No, the pipeline will break | Yes, it will only be slower |
| Lifetime | Days or weeks, configurable | Until eviction or key invalidation |
| Visibility in the interface | Available for download | Internal, usually not shown |
⚠️ About the lifetime of artifacts: in cloud platforms artifacts occupy the storage quota, therefore expire_in (GitLab) and retention-days (GitHub) are worth setting deliberately. There is no sense in keeping builds from six months ago in CI storage: release artifacts must live in a registry or a package storage, and not in CI. The opposite case is painful too: the artifact with the failure report disappeared a day before somebody got round to looking at it.
4. What CI/CD gives in practice
The abstract benefit of automation is clear to everyone. It is more useful to look at what exactly changes in the daily work.
4.1 Fast feedback
The main value of CI is not in saving time on running tests, but in reducing the interval between a mistake and its detection. The cost of fixing depends directly on this interval.
| When the mistake was found | What is needed to fix it |
|---|---|
| 2 minutes after the push | The developer still remembers the context, 20 lines changed, the fix takes minutes |
| A day later, during code review | You have to return to the task, re-read your own code, agree with the reviewer |
| A week later, on staging | Other people's changes already lie on top, an investigation is needed into who exactly broke it |
| On production, from the client | An incident, a rollback, communication with the client, a post-incident review |
The side effect of fast feedback is more pleasant than the obvious saving: developers start making smaller commits. When a check takes two minutes, there is no reason to accumulate changes. And smaller commits mean a simpler review and a more precise answer to the question "after which change did it break".
4.2 Identical conditions in all environments
The pipeline is executed in a described environment: a specific image, a specific interpreter version, dependencies from the lock file. This environment is described in the repository next to the code, therefore it is the same for everyone and changes only through a pull request.
The consequences of this go beyond the build itself:
- Updating versions becomes manageable. Moving from Node.js 20 to 22 is one line in the config, and the pipeline immediately shows what exactly will break.
- Onboarding becomes simpler. A new developer reads the pipeline config and sees the exact list of what the project needs.
- Production stops being a unique server. If a deploy is the rollout of the very image that passed the tests, the difference between environments comes down to configuration and data.
4.3 Deploy becomes a routine, not an event
When shipping is one action that is performed identically and has a predictable rollback, the very culture of work changes. Instead of two-week releases daily ones appear, and each of them contains few changes, therefore the risk of each individual release is small.
This works in the opposite direction too: when shipping is cheap, the team more willingly ships small improvements that used to be postponed until "the next big release". Fixing the text of an error, a small query optimisation, updating a dependency stop waiting for their turn.
ℹ️ What CI/CD does not do: automation does not improve the quality of the code by itself. A pipeline without tests is simply a faster way to deliver to production what nobody checked. In the same way CI/CD does not save you from bad architecture: if rolling out the application requires manual intervention in the database, automation around this will leave the problem in place. First the process has to be reproducible, and only then automated.
5. Popular tools
The CI/CD market is divided into three groups: cloud platforms built into git hosting; self-hosted git platforms with their own CI; separate CI systems that work with any repository.
5.1 Cloud platforms
- GitHub Actions. Launched in 2019, now the most widespread option thanks to the enormous marketplace of ready-made actions and to the fact that most open-source projects live on GitHub. The config is in
.github/workflows/, free minutes for public repositories, a limited quota for private ones. - GitLab CI. Built into GitLab since 2015 and historically stronger in the part concerning complex pipelines: stages, a dependency graph, child pipelines, built-in environments with a deployment history. The config is in
.gitlab-ci.yml. Available both in the cloud and in your own installation. - Bitbucket Pipelines. A logical choice for teams that already work in the Atlassian ecosystem together with Jira. Simpler than the competitors, with tighter integration with tasks.
- CircleCI. An independent platform with a reputation for being fast: flexible tuning of resources for a task, reusable configurations (orbs), a developed cache. Connects to GitHub, GitLab and Bitbucket.
A common feature of this group: nothing has to be administered, payment is for the minutes used or according to a pricing plan. The common limitation: quotas, limited resources on standard runners and the fact that your code is built on someone else's infrastructure.
5.2 Self-hosted Git with built-in CI
- Gitea. A lightweight git platform in Go that occupies tens of megabytes of memory and starts as a single binary. Since version 1.19 it has built-in CI (Gitea Actions), compatible with the GitHub Actions syntax: most simple workflows are ported almost without edits.
- Forgejo. A fork of Gitea under the community's governance, with the same Actions mechanism. It is chosen by those for whom the project governance model and a fully free licence matter.
- Gogs. The predecessor of Gitea, even lighter and even simpler, but without built-in CI. It is used where a minimal git hosting is needed, and CI is connected separately.
This group makes sense when the code must not leave your infrastructure: customer requirements, regulatory restrictions, work in a closed perimeter. The price of the question is the administration itself: backups, updates, availability.
5.3 Separate CI systems
- Jenkins. A veteran of the industry since 2011, with thousands of plugins and the ability to integrate with practically anything. It pays for this with complexity: its own language for describing pipelines, noticeable maintenance costs, regular plugin updates. Still widespread in large companies with accumulated automation.
- Drone CI. A container CI system with simple YAML: every step is the start of a container. Light and understandable, but after the move under Harness the licensing terms changed, which is worth taking into account when choosing.
- Woodpecker CI. A fork of Drone under a free licence, developed by the community. It preserves the simplicity of the original and works well in a pair with Gitea or Forgejo, therefore it is a popular choice for a fully self-hosted stack.
5.4 Comparison and the choice for the series
| Tool | Where it lives | Self-hosted runner | Who it suits |
|---|---|---|---|
| GitHub Actions | GitHub cloud | Yes | Most teams, open source |
| GitLab CI | Cloud or your own server | Yes | Complex pipelines, closed perimeter |
| Bitbucket Pipelines | Atlassian cloud | Yes, with limitations | Teams on Jira |
| CircleCI | Cloud | Yes, in paid plans | Projects with speed requirements |
| Gitea / Forgejo Actions | Your server | Only these | Fully own infrastructure |
| Jenkins | Your server | Only these | Legacy automation, complex integrations |
| Woodpecker CI | Your server | Only these | A light self-hosted stack |
In the next articles of the series we will focus on GitHub Actions and GitLab CI, and the reason is purely practical. Firstly, these are the widest ecosystems: almost any task already has a ready-made example. Secondly, both platforms allow you to connect your own runner on your server without raising your own git platform. That is, the code can stay in the familiar GitHub or GitLab, while the build and the deploy will be performed on your hardware, with your resources and your rules of access to production.
The syntax of Gitea Actions almost repeats GitHub Actions, therefore everything written in the series about workflows will be applicable there too, with a correction for the absence of a part of the ready-made actions from the marketplace.
6. Conclusion
CI/CD is not a separate tool that is installed, but a way to describe the process of code delivery in the form of code. The three practices behind the abbreviation differ in how far the automation stretches:
- Continuous Integration checks every change automatically and gives an answer in minutes instead of days. This is the cheapest step with the biggest return, and it is exactly the one to start with.
- Continuous Delivery brings a checked change to the state of a ready release and ships it to staging, leaving the decision about production to a human.
- Continuous Deployment removes this decision too, but requires mature tests, observability and a rollback mechanism.
Any pipeline consists of the same blocks: the trigger determines when to start, the stages set the order, the jobs do the work, the artifacts pass the results further. Knowing these four concepts, you can read the config of any CI system, even if you are seeing the syntax for the first time.
The practical result is measurable too: mistakes are found in minutes, environments stop drifting apart, and shipping turns from an event into a routine that is done daily and without meetings.
What comes next in the series
In the second part we will take apart the architecture of a self-hosted runner: how the agent that executes your jobs is arranged, why it is worth moving it to your own server (resources, access to a closed network, cache, cost at a large volume of builds), how to install it for GitHub Actions and GitLab CI, and most importantly, how to isolate the execution of someone else's code so that the runner does not become the weakest point of your infrastructure.
📚 Series navigation:
You are reading part 1 of 5 "What CI/CD is: from manual deploy to automated pipelines".
Next: Part 2. Self-hosted runner: architecture, installation, isolation →
🚀 Infrastructure for your own CI/CD runners
Builds and tests are a load that runs into CPU, disk operations and the network. Hostiserver gives predictable resources for this: dedicated servers for build nodes and VPS for light runners, with a network into which a deploy to production can be safely let.
🖥️ Dedicated Servers
- From $90/mo, full control over the hardware for heavy builds
- NVMe drives: a fast cache of dependencies and Docker layers
- No build minutes limit: you pay for the server, not for the pipeline time
- A private network between the runner and the production servers
- 24/7 support: engineers will help with setting up runners and deployment
💻 Cloud (VPS) Hosting
- From $19.95/mo, KVM isolation, dedicated vCPU and RAM
- Ideal for the first runner: bring it up, connect it to the repository, check it on a real project
- Easy to scale: add one more runner for the growth of the team
💬 Not sure which option you need?
💬 Write to us and we will help with everything!
Frequently asked questions
- Does a team of two developers need CI/CD?
Yes, but in a minimal form. Even for two people an automatic build and a test run on every pull request are useful: this removes the question "did you check before merging" and catches conflicts between two parallel tasks. Complex things (staging, canary deploy, automatic rollback) are excessive for such a team. A sensible start is one workflow file of 15 lines that does
buildandtest. The deploy can be left manual until it starts getting in the way.
- How does an artifact differ from a cache?
An artifact is the result of work that the next jobs need: the built frontend, a binary, a test report. If the artifact disappears, the pipeline will break. A cache is an optimisation: saved
node_modulesor downloaded packages that speed up the next run. If the cache disappears, the pipeline will simply work more slowly. A practical rule: cache what can be restored from the network, and put into artifacts what your pipeline itself created.
- How much does CI/CD cost and when is your own runner more profitable?
Cloud platforms give a free quota of minutes that is enough for a small project, after which payment is for usage. The exact limits change, so check against the platform's current pricing. Your own runner becomes profitable in three scenarios: the builds are long and frequent (the quota is exhausted every month), non-standard resources are needed (a lot of RAM, GPU, specific hardware), or the pipeline has access to a closed network that a cloud runner will not reach. Apart from the cost, your own runner gives a predictable build time without queues during the hours of peak load. We will take this apart in detail in the second part of the series.
- Is it possible to do CI/CD without Docker?
It is. Containers are the most widespread way to get a reproducible environment, but not the only one. A runner can execute commands directly on the host system (shell executor), in a virtual machine or in a sandbox of the LXC kind. The downside of working on the host is that the environment gradually gets polluted: leftovers of previous builds, globally installed packages, changes in system settings. Because of this builds become non-reproducible, and errors dependent on which pipeline was run earlier. If you give up containers, plan for cleaning the working directory and pinning the versions of tools.
- Where to start: Continuous Delivery or Continuous Deployment?
With Delivery, that is, with a manual button to production. The reason is simple: the move to a fully automatic deploy is not a technical decision but a check of the maturity of the tests and the monitoring. Several months of work with the manual step will give the answer to the main question: were there cases when a person, looking at a green pipeline, decided not to ship? If there are no such cases, the manual step has become a formality and it can be removed. If there were, first you have to understand what the automation did not see.
- How to store the passwords and SSH keys that the pipeline needs?
Only in the secrets mechanism of the platform itself (Secrets in GitHub, CI/CD Variables in GitLab) or in an external storage of the HashiCorp Vault kind. There must not be a single secret in the repository, not even in a private one, not even temporarily: the git history keeps everything, and deleting the file in the next commit fixes nothing. A few basic rules: mark variables as masked and protected, give deploy keys minimal rights (a separate user on the server instead of root), limit access to production secrets to one protected branch. For pull requests from external forks secrets are unavailable by default, and it is not worth switching this protection off.
- Is there sense in CI/CD if the project has almost no tests?
There is, although the benefit will be smaller. Even without tests the pipeline checks that the project builds in a clean environment, and this catches the typical problem with unpinned dependencies. Then a linter, a formatting check, scanning of dependencies for vulnerabilities, a check of migrations are connected. This gives basic protection and, more importantly, ready infrastructure: when the first tests appear, they will only have to be added with one line to the existing workflow. The reverse order (first writing tests for years, and then building CI) in practice means that there will be neither one nor the other.