If you want to run servers and ship software, learn it in this order: Linux and the shell, then one small server that you take all the way from a blank machine to a working, monitored, backed-up application, then systemd, Nginx, Docker and a deploy pipeline - in that order, each one replacing something you have already done by hand. Kubernetes, service meshes and multi-cloud come later, if they come at all. Most people who stall at DevOps stall because they started at the top of that list instead of the bottom.
We run the RE:NODE hosting platform, which runs more than 1,200 servers for over 500 clients. Running it shows which skills actually keep a fleet alive at night, and the way people commonly learn shows which of those skills get skipped. This post is the path we would give anyone starting from zero, with the reasons, because the reasons are what let you judge the next tool on your own.
The order, and why it is this order#
The principle is simple: never automate something you have not done by hand. Every DevOps tool is a wrapper around a manual job - starting a process, routing a request, copying files to a server, checking whether a thing is up. If you learn the wrapper first, you can follow a tutorial, but the first time it breaks you have nothing underneath it to fall back on.
| Stage | What you learn | You are done when |
|---|---|---|
| 1. Linux and shell | Files, permissions, processes, logs | You can find why a process died |
| 2. One VPS | SSH, firewall, users, updates | You can rebuild it from notes |
| 3. systemd | Services, restarts, journald | Your app survives a reboot |
| 4. Nginx | Reverse proxy, TLS, static files | Your app has a domain and HTTPS |
| 5. Docker | Images, volumes, networks, Compose | You can move the app to a new box |
| 6. CI/CD | Build, test, deploy on push | Nobody runs scp by hand |
| 7. Monitoring and backups | Alerts, restores | You have restored a backup on purpose |
Each stage has a "done when" for a reason. Learning materials are good at telling you what a tool does and bad at telling you when you understand it well enough to move on. A concrete finish line stops you from either rushing or polishing forever.
Monitoring and backups sit at the end of the table only because they need something to watch and something to protect. In practice they start on day one, and we come back to that below.
Linux and the shell before anything else#
Almost everything in this field runs on Linux, and almost every problem you will debug is a Linux problem wearing a costume. A container that will not start is usually a permissions problem, a missing file or a port already in use. A slow deploy is usually disk or network. A "random" crash is often the kernel's out-of-memory killer. You cannot see any of that if the shell is a place you visit only to paste commands.
What someone should be comfortable with before they touch a container:
- Moving around and reading files:
cd,ls -la,cat,less,tail -f,grep,find. - Permissions and ownership: what
rwxmeans,chmod,chown, why a service running aswww-datacannot read a file owned by root with mode600. - Processes:
ps aux,toporhtop,kill, what a signal is, the difference betweenSIGTERMandSIGKILL. - Disk and memory:
df -h,du -sh,free -h, and why "the disk is full" can also mean "out of inodes". - Networking basics:
ss -tlnpto see what is listening,curl -vto see what a request actually does,digto see what a name resolves to. - Logs:
journalctl -u name,/var/log, and the habit of reading the last 50 lines before guessing.
# The five commands to answer before anyone says "it doesn't work"$ systemctl status myapp # is it running, and what did it last say$ journalctl -u myapp -n 50 # the last 50 lines it logged$ ss -tlnp # what is listening, on which port, as which process$ df -h && free -h # disk and memory, the two boring killers$ curl -v http://127.0.0.1:3000/ # does it answer locally, before blaming DNSThe shell skill that matters most is not memorising flags. It is the habit of asking the machine instead of guessing. Beginners commonly change three things at once, restart, and hope. A better rule is one change, one restart, read the log. It is slower for ten minutes and much faster for the rest of your career. A longer reference is in Linux commands for server admins.
Learn enough Bash to write a twenty-line script with a variable, an if, a loop and set -euo pipefail at the top. You do not need to be good at Bash. You need to be able to read the scripts other people leave on servers, because there will be many.
One VPS, end to end#
The single most useful exercise is this: rent one small server, and take it from a fresh install to a real application that you would be willing to show someone. Not a cluster, not three providers, one machine. Doing the whole path once teaches you how the pieces connect, which no amount of studying the pieces separately will.
The path, in order:
- Log in with SSH, create a normal user with
sudo, and switch to key-based login. Turn off password login and root login once the key works - not before, or you lock yourself out. - Turn on a firewall that allows SSH first, then only the ports you need. On Ubuntu that is
ufw allow OpenSSH, thenufw enable. - Turn on automatic security updates, and learn how to reboot the machine without fear.
- Install your runtime and run the app by hand, in the foreground, until it works.
- Make it a systemd service.
- Put Nginx in front of it with a domain and a certificate.
- Add a backup, an uptime check and a disk alert.
- Write down every step, then destroy the server and rebuild it from your notes.
Step 8 is the one people skip and the one that teaches the most. The first rebuild is when you discover the setting you changed and forgot, the package you installed while debugging, the file you edited in /etc at midnight. That gap between "what you think is on the server" and "what is actually on the server" is the problem configuration management and containers exist to solve. Feel it once and you will understand why the tools exist, instead of just learning their syntax.
The first steps are covered in detail in the first hour on a new VDS and SSH keys and hardening.
How to practise on a cheap server
You do not need much. A machine with 1-2 GB of RAM and one or two cores runs a small web app, Nginx, a database and a monitoring agent comfortably for learning purposes. What matters more than the size:
- It must be a real public server. A virtual machine on your laptop is fine for the shell, but it hides the parts that hurt: DNS, certificates, a firewall facing the internet, bots trying SSH passwords within minutes of the address going live.
- It must be disposable. Treat it as something you will wipe and rebuild. If you are afraid to break it, you will not learn on it.
- Give it a domain. A cheap domain pointed at it with an A record lets you practise TLS, redirects and virtual hosts properly instead of working around them.
- Set a budget alert or a fixed-price plan. Cloud accounts with per-minute billing and no cap are a bad place to learn, because the expensive mistake is the one you do not notice.
Break it on purpose. Fill the disk with fallocate -l and watch what fails first. Kill the app with kill -9 and check whether systemd brings it back. Stop Nginx and see what the browser says. Delete the database and restore it. Every one of those is a real failure you will meet later, and meeting it on a practice box is free.
systemd: the part everyone skips#
Beginners commonly go straight from "it runs in my terminal" to "it runs in Docker", and skip the layer that actually keeps processes alive on a Linux machine. That is a mistake, because even if you end up running everything in containers, the container runtime itself is a systemd service, and the debugging skills transfer directly.
A unit file is short, and writing one by hand teaches you what "running in production" means: a fixed user, a working directory, an environment, a restart policy, and logs that go somewhere you can read them.
[Unit]Description=My appAfter=network-online.targetWants=network-online.target[Service]User=myappWorkingDirectory=/opt/myappEnvironmentFile=/etc/myapp/envExecStart=/usr/bin/node /opt/myapp/server.jsRestart=on-failureRestartSec=5[Install]WantedBy=multi-user.targetThen systemctl daemon-reload, systemctl enable --now myapp, and journalctl -u myapp -f to watch it. If you understand why Restart=on-failure is there, why the app does not run as root, and why secrets live in an EnvironmentFile with tight permissions rather than in the unit itself, you understand more about production than many people who list Kubernetes on their CV. The full treatment is in systemd services for your apps.
Nginx in front of everything#
The next layer is the reverse proxy. Your app listens on 127.0.0.1:3000, and Nginx listens on ports 80 and 443, terminates TLS, serves static files and forwards everything else. This is the pattern behind most of the web, and learning it by hand makes every managed load balancer and ingress controller you meet later obvious.
server { listen 80; server_name app.example.com; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}Get a certificate with Certbot, check that it renews with certbot renew --dry-run, and run nginx -t before every reload. The things worth understanding rather than copying: why the app should listen on localhost and not on 0.0.0.0, what the forwarded headers are for, and why your app logs show 127.0.0.1 for every visitor until you tell it to trust the proxy. The Nginx reverse proxy guide goes further, including WebSockets and timeouts.
Docker, once you know what it replaces#
Docker is where the most confusion shows up, and almost all of it comes from learning it too early. If you have already run an app as a systemd service behind Nginx, Docker is easy to place: an image is a packaged version of the files and runtime you installed by hand, a container is the process systemd was running, a volume is the data directory you had to remember to back up, and a published port is the listen line you already understand.
What a learner should get from Docker at this stage:
- Write a
Dockerfilefor your own app, not just pull other people's images. - Understand layers well enough to order the file so dependencies are cached.
- Know that a container's filesystem is thrown away, and anything that matters lives in a volume.
- Use Docker Compose to run the app and its database together, with a named volume for the data.
- Publish ports deliberately, and know that Docker's published ports can bypass
ufwrules on the same host, because Docker writes its own iptables rules.
services: app: build: . restart: unless-stopped env_file: .env ports: - "127.0.0.1:3000:3000" depends_on: - db db: image: postgres:16 restart: unless-stopped environment: POSTGRES_PASSWORD_FILE: /run/secrets/db_password volumes: - dbdata:/var/lib/postgresql/data secrets: - db_passwordvolumes: dbdata:secrets: db_password: file: ./db_password.txtNote the 127.0.0.1: in front of the published port. Without it the app is reachable from the internet directly, around Nginx and around the firewall you thought you had. That one detail is a good test of whether someone learned Docker or copied it. More in Docker on a VDS and Docker Compose for small stacks.
CI/CD: automate what you already do by hand#
By this point you have deployed by hand several times - pull the code, install dependencies, build, restart the service. That manual sequence is your pipeline. CI/CD is writing it down in a file so a machine runs it the same way every time, with tests in front so a broken commit stops before it reaches the server.
Start with the smallest pipeline that is honest: run the tests on every push, and deploy the main branch when they pass. GitHub Actions is a reasonable place to learn because the free allowance covers this easily and the syntax is everywhere.
name: test-and-deployon: push: branches: [main]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm test deploy: needs: test runs-on: ubuntu-latest steps: - name: Deploy over SSH run: | echo "${{ secrets.DEPLOY_KEY }}" > key && chmod 600 key ssh -i key -o StrictHostKeyChecking=accept-new \ deploy@${{ secrets.HOST }} 'cd /opt/myapp && ./deploy.sh'The deploy job just calls a deploy.sh that lives on the server - the same script you would run by hand. That is better than putting the whole deploy into the workflow file, because when the pipeline breaks you can SSH in and run the script yourself to see what happens. Give the deploy key its own user with access to that one directory and nothing else.
Two things are worth insisting on early. First, a deploy must be repeatable: running it twice in a row should be harmless. Second, there must be a way back: keep the previous release, or the previous image tag, so a rollback is one command and not an archaeology project. On RE:NODE, orchestration takes an average deploy to under five minutes, and the speed matters less than the fact that every step is the same every time - that is what makes it safe to do often.
Monitoring and backups belong in the first deploy#
This is the uncomfortable part. Most learning paths treat monitoring and backups as advanced topics, to be added "later". Later usually means after the first time something is lost. We treat them as part of the first deployment, because a service without either is a demo, not a service.
The minimum is small:
| What | Why | Smallest version |
|---|---|---|
| Uptime check from outside | You hear about outages before users do | A free external HTTP check |
| Disk alert | Full disks break everything quietly | Alert at 80% used |
| Logs you can search | You can answer "what happened at 03:00" | journald with persistent storage |
| Off-machine backup | The server itself can be lost | Nightly dump copied elsewhere |
| A tested restore | Proves the backup is real | Restore to a scratch server monthly |
A backup nobody has restored is a hypothesis. The test is simple: take the database dump from last night, restore it onto a fresh machine, and start the app against it. If you cannot do that from your notes, you do not have backups yet. Say a node's disk fills at 3am - the useful questions are whether an alert reached a person before it filled, and whether the data from before it filled exists somewhere that is not that disk.
Monitoring has the opposite failure. Beginners commonly install a dashboard with two hundred graphs and no alerts, which is decoration. Start with three or four alerts that each mean "a person should do something now", and add more only when an incident shows you a gap. The rule worth keeping is that an alert nobody acts on should be deleted, not muted. See monitoring that tells you something and backups that actually restore.
What to skip at the start#
Skipping is a skill. The field has more tools than any one person can learn, and most of them solve problems you do not have yet. This is what to leave alone until the basics are boring:
| Skip for now | Why | Come back when |
|---|---|---|
| Kubernetes | Solves many-machine scheduling | You run several hosts |
| Terraform at scale | Shines with many cloud resources | Rebuilding by hand hurts |
| Service meshes | Solves traffic between many services | You have many services |
| Multi-cloud | Doubles everything to learn | Never, for most people |
| Microservices | Adds network failure everywhere | One app is too big |
| Ten certifications | Proves study, not operation | You need one for a job |
Kubernetes deserves a direct word, because it is the thing beginners most often want to start with. It is a good tool for scheduling containers across many machines. But every concept in it - pods, services, ingress, persistent volumes, probes, secrets - is an abstraction over something in the earlier stages: a process, a port, a reverse proxy, a disk, a health check, an environment file. If you learn Kubernetes first, you learn the abstractions without the things they abstract, and debugging becomes guesswork. If you learn it after one VPS, Docker and Nginx, most of it reads as "oh, that is the thing I already did, declared in YAML".
We are not saying Kubernetes is bad or that you will never need it. We are saying that for a single application on one or two servers it is a large amount of machinery protecting you from problems you do not have, and that learning it first slows you down.
Misconceptions beginners commonly bring#
These are patterns common enough to address at the start, not stories about anyone in particular.
- "DevOps is a set of tools." It is a way of working: the people who write software also own how it runs, and anything done twice gets automated. The tools change every few years. The habits do not.
- "If it works on my machine, the server is the problem." Usually the difference is in configuration, versions, environment variables or file paths. Finding that difference is the job.
- "Containers are secure by default." A container running as root, with a published port and a mounted host directory, is not isolated in any way that matters. Containers are packaging first.
- "More tools means more professional." The best setups are boring: few moving parts, each understood. Every tool you add is something to update, monitor and debug.
- "Root is fine on my own server." It is fine until a vulnerable app running as root hands an attacker the whole machine. Separate users cost nothing.
- "Backups are something the provider does." Sometimes. Find out exactly what is backed up, where it is stored, and how a restore works, before you need one.
- "I need to memorise commands." You need to know what is possible and where to look. Reading
manpages and logs is the skill. Memory follows use.
The most expensive misconception is the quietest one: that understanding comes from watching. Courses, videos and tutorials are useful, but none of this sticks until you have broken a real server and fixed it yourself. The ratio to aim for is far more time in a terminal than in front of slides.
What running a hosting platform teaches about the order#
Running RE:NODE means automated provisioning, scaling and recovery across dozens of nodes and 30+ services, with RBAC, monitoring, backup automation and incident response on top. None of that started as sophisticated tooling. It started as manual steps that were understood, which became scripts, which became services, which became orchestration. Every layer of automation was safe to build because the layer underneath was already understood.
That is the whole argument of this post, really. The order is not arbitrary. Each stage turns the previous one into something you can stop thinking about, and you can only safely stop thinking about something you once thought about carefully. People who start at the top have to work downwards when things break, at the worst possible time. People who start at the bottom work upwards at their own pace.
AI coding agents have made this more true, not less. An agent will happily write a Dockerfile, a unit file or a pipeline in seconds. Whether that file is correct, secure and suited to your server is still a judgement only someone who understands the layers can make. The skill that is getting cheaper is typing configuration. The skill that is getting more valuable is reading it.
FAQ#
How long does it take to learn the basics of DevOps?
It depends on how much time you spend in a terminal rather than on reading. Working through the stages above on a real server - including rebuilding it from notes and restoring a backup - is a matter of months of steady practice, not weeks. Being comfortable with Linux is the stage that takes longest, and it is worth every hour.
Do I need to know programming before learning DevOps?
You need enough to read code and write small scripts: Bash for gluing commands together, and one general language such as Python for anything longer. You also need to understand how the applications you deploy are built and configured, because most deployment problems live at the boundary between the app and the server.
Should I learn AWS, Azure or Google Cloud first?
None of them first. Learn on one plain Linux server, where nothing is hidden behind a console. Cloud platforms rename and wrap the same building blocks - machines, disks, networks, firewalls, load balancers - and they are much easier to learn once you know what those blocks are. Pick a cloud later based on the job you want.
Is Docker or Kubernetes more important to learn?
Docker, by a wide margin, and well before Kubernetes. Almost every modern deployment uses container images somewhere, and you cannot use Kubernetes properly without understanding images, volumes and networking first. Many real systems run happily on Docker Compose on one server and never need more.
Can I practise DevOps without paying for a server?
You can learn the shell, Docker and scripting in a local virtual machine for free. What you cannot practise locally is the public internet: DNS, certificates, a firewall facing real traffic, and the constant background of login attempts. A small paid server is worth it once you reach the end-to-end stage.


