Linux in 20 minutes

Most servers run Linux. Learn the shell, files, processes and permissions.

20 min read

In the last lesson, a request found its way to your app. The app was waiting on a server, and that server almost certainly ran Linux.

You will see Linux every day at work: in CI logs, in Dockerfiles, in the terminal of a broken server. You do not need to run it alone. You need to read it.

In this lesson you learn four things: the shell, the file tree, processes and permissions.

Why servers run Linux

is free and open source. Anyone can run it on any number of servers without a licence fee, and anyone can read and fix its code.

So the cloud runs on it, and most container images you will use are Linux images. Docker containers need a Linux kernel, so on a Mac, Docker Desktop starts a small Linux virtual machine in the background to run them.

The good news: your Mac is already close.

  • macOS and Linux are both in the Unix family.
  • Your Mac terminal runs zsh, a . Most servers and CI images use bash, a near relative.
  • ls, cd, cat and grep work the same on both.

The shell and ten commands

A server has no screen and no mouse. You talk to it through the shell, often over . CI robots talk to it the same way: every run: line in a GitHub Actions file is a shell command.

These ten commands cover most of what you will read in logs and Dockerfiles:

CommandWhat it does
ls -lList files, with details
cd /var/logGo into a folder
cat app.logPrint a file
grep ERROR app.logShow lines that match
tail -f app.logFollow a file as it grows
ps auxList running programs
kill 4127Send a signal to a program
chmod +x deploy.shChange who can do what
sudo apt-get install curlRun one command as admin
curl localhost:3000Send an HTTP request

Commands join with a . ps aux | grep node lists every program, then keeps only the lines with "node". You will see this pattern often.

The shell does what you type, not what you mean. Read a command twice before you press Enter on a production server.

The file tree

Linux keeps everything in one tree. It starts at /, called the root. There are no drive letters.

The folders you will meet most often on a server.
  • /etc holds settings. A web server such as nginx reads its settings from /etc/nginx.
  • /var/log holds logs. When a server misbehaves, look here first.
  • /home has one folder for each user, like /Users on your Mac.
  • /tmp is for short-lived files. Do not keep anything there that you need.
  • /app is not a standard folder. Many Dockerfiles copy the app there with WORKDIR /app.

Processes and signals

A running program is a . Linux gives each one a number, the .

When you deploy a Next.js app to a server, next start runs as one process.

Here is a small Node server on a Mac, seen with ps. Linux shows the same columns. The path to node is shortened.

Terminal
$ ps -o pid,user,command -p 10037
  PID USER            COMMAND
10037 praveen-netinti node server.js

Inside a container, the PID numbers start again from 1. Your app is often PID 1, the first and main process.

How a platform stops your app

You do not stop a process by hand. You send it a . Three signals matter most:

SignalNumberMeaning
SIGINT2Ctrl+C in the terminal
SIGTERM15Please finish and stop
SIGKILL9Stop now, no clean-up

Every deploy stops old copies of your app.

docker stop sends SIGTERM first. If the process is still running after 10 seconds, Docker sends SIGKILL. A process can catch SIGTERM, but it cannot catch SIGKILL.

So a good app listens for SIGTERM. It finishes the requests it has, then exits. The Next.js server does this for you. With a plain Node server, you add a handler:

Node.jsserver.js
const server = require("node:http").createServer((req, res) => res.end("hi"));
server.listen(3000);
process.on("SIGTERM", () => {
console.log("SIGTERM: finishing requests, then exit");
server.close(() => process.exit(0));
});
  • process.on("SIGTERM", ...) runs your code when the signal arrives.
  • server.close stops new requests and waits for the open ones. Then the process exits.
Terminal
$ kill -TERM 10037
# the server's own terminal then prints:
SIGTERM: finishing requests, then exit

If your app ignores SIGTERM, users can see broken requests during every deploy.

Users and permissions

Every process runs as a user. One user is special: root, user number 0, can read, change and delete anything. sudo runs one command as root.

Every file has an owner and three sets of permissions. ls -l shows them.

This output is from a Mac. The @ at the end of the first column is a Mac extra; Linux shows the same letters without it.

Terminal
$ chmod 640 notes.txt
$ ls -l notes.txt
-rw-r-----@ 1 praveen-netinti  wheel  3 Nov  8 08:08 notes.txt
Ten letters: the type, then three sets of three permissions.

chmod 640 sets the same thing with numbers. Read is 4, write is 2 and run is 1. Add them for each set: 6 for the owner (read and write), 4 for the group, 0 for others.

Why containers should not run as root

Unless the Dockerfile says otherwise, many images run your app as root. If an attacker finds a bug in your app, they get root powers inside the container. Root in a container is also user 0 on the host, so any host files shared into the container are open to it.

The fix is one line. The official Node.js images include a user called node:

Dockerdockerfile
FROM node:24-slim
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["npm", "start"]

USER node tells Docker to run the app as that user, not as root. A bug in the app can now only damage what node owns.

Remember4 points
  1. 01

    Most servers and container images run Linux. Your Mac terminal uses the same commands.

  2. 02

    Settings live in /etc, logs in /var/log. Everything hangs from one root, /.

  3. 03

    Each running program is a process with a PID. Platforms stop it with SIGTERM, then SIGKILL, so handle SIGTERM.

  4. 04

    Permissions are read, write, run for owner, group and others. Do not run containers as root.

Read more

Next, you learn how load balancers, proxies and CDNs spread traffic across many servers.