Run a Real App by Hand: Nginx, TLS, Postgres and Backups

By Branko Petric - Updated - About 3 weeks at 10 hours a week

The short answer

Before learning DevOps tools, run one real app by hand. Put a small API and Postgres on a server, behind Nginx, on HTTPS, under systemd, with a backup you have restored. Skip database replication and load-balancing theory. Later tools automate these same steps. Plan on about 3 weeks at 10 hours a week.

Why you run an app by hand now

Most DevOps tools solve a problem you can only feel after doing the job manually. If you learn the tool first, it looks like a pile of settings to memorize. If you do the job first, each tool looks like the answer to a chore you already know.

Here is what this step sets up:

  • Your install notes become a Dockerfile (a file listing the steps to build a container).
  • Your deploy steps become a CI/CD pipeline.
  • The server you set up by clicking and typing becomes Terraform code.
  • Restart=on-failure in systemd becomes a restart policy in Kubernetes.
  • Your Nginx config becomes a load balancer or an Ingress (the Kubernetes rule that routes web traffic).
  • The 502 you caused on purpose becomes a real alert at observability.

It comes after scripting because you need a backup script, and because you need a small app to run. You can write a very small API in Python, or use any small app you know.

What to learn, in order

  1. Run the app under systemd. The app should listen on 127.0.0.1 on a port such as 8000, and it should run as its own Linux user. Set Restart=on-failure in the unit file, so a crash does not need you to step in. The systemd guide covers the unit file.
  2. Install Postgres. By default on Ubuntu, it listens only on its own machine, and you should keep it that way. Create a separate database and user for the app. A leaked app password then cannot touch anything else.
  3. Store secrets outside the code. Put the database password in a file only the app's user can read, and load it with EnvironmentFile= in the unit file. Do not commit that file to Git.
  4. Put Nginx in front. Nginx is a web server. Here it works as a reverse proxy (a program that takes requests from the internet and passes them to your app). Start with the beginner's guide. Check every edit with sudo nginx -t before you reload it.
  5. Point a domain at the server. Create an A record from your domain to the server's public IP address, then confirm it with dig. You learned this at step 2.
  6. Lock down the firewall. With ufw, allow only SSH and ports 80 and 443. Database and app ports stay closed to the internet.
  7. Add HTTPS. Learn how Let's Encrypt checks that you control a domain. Then use Certbot, a tool that gets and renews certificates. As of October 2026, Let's Encrypt says its default certificates last 90 days, so automatic renewal is essential.
  8. Back up the database. Run pg_dump from a script every night. Copy the file off the server, because a backup on the same disk dies with that disk.
  9. Restore it. Create a new database and load the backup into it. Use a new database name, so a mistake cannot overwrite your live data. Until you have done this, you do not know that your backup works.

What to skip for now

  • Database replication and high availability setups.
  • Query tuning and Postgres internals.
  • Apache and other web servers. One is enough.
  • Load-balancing algorithms.
  • Fancy Nginx caching and rate-limit settings.
  • Automation of any kind. Typing these steps yourself is the whole point.

Common mistakes

  • Opening port 5432 to the internet to connect from your laptop. Use an SSH tunnel instead, such as ssh -L 5432:localhost:5432 you@your-server. It keeps the database private.
  • Changing Nginx when you see a 502. A 502 usually means Nginx is fine and the app behind it is not. Check the app first with systemctl status and journalctl.
  • Assuming a backup works because the job ran. A job can succeed and still produce an empty or useless file. Only a restore proves it.
  • Assuming renewal works because the first certificate worked. Test it with sudo certbot renew --dry-run. An expired certificate takes your site down for every visitor.
  • Running the app as root. A bug in the app then has full control of the server. Create a separate user.
  • Not writing the steps down. You need this list later, because each step you record is a step you will automate.

Stepping-stone jobs

This is a good point to start looking at helpdesk, IT support and junior sysadmin jobs. You can now show that you can run a real service and repair it. These roles are a common way into DevOps work.

This is a judgment call, not a promise. Time spent on real systems, with real users and real outages, teaches things a home project cannot. You can keep working through this roadmap while you do one of these jobs.

Your project

This app is the project you will carry through the rest of the roadmap. Pick something small: an API with two or three routes that reads and writes one Postgres table.

  1. Set it up on a server, working through the list above.
  2. Keep each command and config file in your runbook, in Git. Leave out the secrets.
  3. Add data, take a backup, then delete the server.
  4. Rebuild from your runbook on a new server, and restore the data.

Warning: only delete the server after you have copied the backup off it.

The rebuild shows you which notes were missing. It also shows how slow and error-prone manual work is. That feeling is your reason for learning Docker next.

You are done when

You can do all of this with no notes. If you already can, skip this step.

  • curl -I https://your-domain returns a success status with a trusted certificate, and sudo certbot renew --dry-run succeeds.
  • You run sudo kill -9 on the app's process, and systemd restarts it. After a full reboot, the app comes back with no help from you.
  • ss -tln shows Postgres listening only on 127.0.0.1:5432, and the app connects as its own database user, not postgres.
  • You stop the app and see a 502 from Nginx, then you explain that error and find the cause with journalctl -u <service>.
  • You restore last night's pg_dump backup into a new, empty database, and the row counts match your live database.

Learn it and test it

Free guides

Practice scenarios

Common questions

Why deploy by hand when tools can do it for me?

Tools hide the steps, but they do not remove them. When a pipeline or container fails, you still have to work out which step broke. If you have done each step yourself, the error makes sense. If you have not, you are guessing.

What entry-level job leads to DevOps?

Common stepping stones are IT support, helpdesk and junior sysadmin roles, and software development is another route. They put you near real systems and real outages. No job or path is guaranteed, but this is a sensible time to start applying for them.

Can I do this on a local virtual machine?

You can learn most of it locally. The public certificate step is the exception. A certificate authority such as Let's Encrypt usually has to reach your server from the internet to confirm that you control the domain. For that you need a small rented server and a domain name. Both cost money, so check the price first.

Which database should I use?

Use Postgres. It is widely used and well documented, and its backup tools are easy to practice with. The aim here is to learn how to run, secure and restore a database. You can apply the same ideas to MySQL later.

Practice defending your decisions

DevOps Ledger is a scenario-based DevOps and SRE interview practice platform: you pick a real production incident, defend your decision in a chat against a senior-engineer AI that pushes back, and get scored against an answer key written before you started.

One payment. 12 months of access. No subscription, nothing renews.

Get full access - $19