Skip to content
DevOps devops app-deployment 6 min read

How Application Deployment Works

Deployment is the act of taking the code that runs on your laptop and getting it running on a server that the whole world (or your company) can reach. It sounds simple, but a real production setup has several moving parts that each solve a specific problem: keeping the app alive, handling web traffic, securing connections, and storing secrets. This page gives you the full mental model first, so the step-by-step “how-to” pages that follow actually make sense.

The journey from code to a running app

When a request reaches your app in production, it passes through a chain of pieces. Here is that chain, from the outside in:

  1. A user’s browser sends an HTTPS request to your domain (e.g. https://example.com).
  2. DNS (the Domain Name System, the internet’s phone book) turns example.com into your server’s IP address (a numeric address like 203.0.113.10).
  3. A firewall on the server (on Ubuntu, usually ufw) decides which ports are allowed in — typically just 80 and 443.
  4. A reverse proxy (a server that sits in front of your app and forwards requests to it — usually Nginx) accepts the connection on port 443, handles TLS, and passes the request to your app.
  5. Your app runs as a long-lived process on an internal port like 3000, kept alive by a process manager.
  6. Your app talks to a database and other services to do its work.

Each layer has a clear job. The rest of this page explains them one by one.

Getting the code onto the server

Before anything can run, the code has to physically arrive on the server. There are three common ways to do this:

MethodHow it worksWhen to use it
git pullSSH into the server and pull the latest commitSmall projects, simple sites, learning
Build artifactCI builds a tarball/Docker image, server downloads itTeams, repeatable builds, rollbacks
scp / rsyncCopy files from your machine over SSHOne-off uploads, static sites

For most beginners, deploying with Git is the easiest start. A typical layout puts the app in /opt/myapp or /var/www/myapp.

sudo mkdir -p /opt/myapp
cd /opt/myapp
sudo git clone https://github.com/your-org/your-app.git .

Tip: Never run your app from inside a user’s home folder like /home/ubuntu. Use /opt or /var/www so the app survives if that user account is removed, and so permissions stay predictable.

The runtime

The runtime is the program that actually executes your code: Node.js for JavaScript, Python for .py files, the JVM (Java Virtual Machine) for .jar files, and so on. The server needs the right runtime installed, and ideally the same major version you used in development.

node --version
python3 --version

Output:

v22.14.0
Python 3.12.3

If the versions don’t match what your app expects, you will hit confusing errors. Pin the version in your project (e.g. an .nvmrc file for Node) and install that exact one on the server.

Keeping the app alive: process managers and systemd

If you just run node server.js in your terminal and log out, the app dies. A process manager is a tool that keeps your app running in the background, restarts it if it crashes, and starts it again after a reboot.

There are two main choices on Ubuntu:

ToolWhat it isWhen to use which
systemdThe built-in service manager on every Ubuntu serverThe production default — any language, no extra install, central logs
PM2A Node-specific process manager you install via npmQuick Node demos, or when you want clustering with simple commands

systemd is already running as process 1 on Ubuntu 22.04 and 24.04, so it needs nothing extra installed. You describe your app once in a small unit file and systemd handles the rest. The dedicated pages below show both approaches.

The reverse proxy

Your app listens on an internal port like 3000, but you don’t want users typing :3000 in their browser — and you definitely don’t want your app exposed directly to the internet. A reverse proxy sits in front of your app on the standard web ports (80 and 443) and forwards each request to it.

On Ubuntu, Nginx is the most common choice. A minimal site config lives in /etc/nginx/sites-available/ and looks like this:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

The reverse proxy also handles TLS, gzip compression, serving static files, and load balancing across multiple app instances — jobs your app shouldn’t waste time on.

Environment config and secrets

Your app needs settings that differ between your laptop and the server: the database URL, API keys, the port, and so on. These are called environment variables (named values the operating system hands to your program when it starts). Never hard-code secrets in your source code or commit them to Git.

On a server, store them in a file readable only by the app’s user, or pass them through the systemd unit file:

[Service]
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/etc/myapp/app.env
sudo chmod 600 /etc/myapp/app.env

Security gotcha: A leaked .env file is one of the most common ways apps get breached. Lock it down with chmod 600 (readable only by its owner) and keep it out of your Git repository with .gitignore.

TLS — encrypting traffic

TLS (Transport Layer Security, the technology behind the padlock in your browser) encrypts traffic between users and your server, turning http:// into https://. In 2026 there is no excuse to skip it — certificates are free from Let’s Encrypt and automated with certbot.

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com

Certbot edits your Nginx config to add the certificate, redirects HTTP to HTTPS, and sets up automatic renewal.

Per-language deployment pages

The concepts above are universal, but the exact commands differ by stack. Follow the page that matches your app:

  • Node.js — run with systemd or PM2, proxy with Nginx.
  • Python — run with Gunicorn behind systemd, proxy with Nginx.
  • Java — run a self-contained .jar as a systemd service.
  • Static sites — no app process at all; Nginx serves files directly.

Best Practices

  • Run your app as a dedicated non-root user, never as root.
  • Put the app on an internal port and expose it only through a reverse proxy.
  • Always enable TLS with a free Let’s Encrypt certificate and auto-renewal.
  • Keep secrets in a chmod 600 env file outside your Git repo.
  • Let systemd (or PM2) restart the app on crash and on reboot — never rely on a manual node server.js.
  • Use a repeatable deploy method (Git pull or build artifacts) so you can roll back quickly when something breaks.
  • Pin your runtime version on the server to match what you tested with.
Last updated June 15, 2026
Was this helpful?