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,catandgrepwork 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:
| Command | What it does |
|---|---|
ls -l | List files, with details |
cd /var/log | Go into a folder |
cat app.log | Print a file |
grep ERROR app.log | Show lines that match |
tail -f app.log | Follow a file as it grows |
ps aux | List running programs |
kill 4127 | Send a signal to a program |
chmod +x deploy.sh | Change who can do what |
sudo apt-get install curl | Run one command as admin |
curl localhost:3000 | Send 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 root of the tree │ │ ├── etc/ settings files │ │ │ └── nginx/ one program's settings │ │ ├── var/ │ │ │ └── log/ log files │ │ ├── home/ │ │ │ └── deploy/ one user's own files │ │ ├── tmp/ scratch space, wiped often │ │ └── app/ your app, in many images │ │ │ └──────────────────────────────────────────────────┘
/etcholds settings. A web server such as nginx reads its settings from/etc/nginx./var/logholds logs. When a server misbehaves, look here first./homehas one folder for each user, like/Userson your Mac./tmpis for short-lived files. Do not keep anything there that you need./appis not a standard folder. Many Dockerfiles copy the app there withWORKDIR /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.
$ ps -o pid,user,command -p 10037
PID USER COMMAND
10037 praveen-netinti node server.jsInside 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:
| Signal | Number | Meaning |
|---|---|---|
| SIGINT | 2 | Ctrl+C in the terminal |
| SIGTERM | 15 | Please finish and stop |
| SIGKILL | 9 | Stop 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:
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.closestops new requests and waits for the open ones. Then the process exits.
$ kill -TERM 10037
# the server's own terminal then prints:
SIGTERM: finishing requests, then exitIf 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.
$ chmod 640 notes.txt
$ ls -l notes.txt
-rw-r-----@ 1 praveen-netinti wheel 3 Nov 8 08:08 notes.txt┌────────────────────────────────────────────┐ │ │ │ -rw-r----- notes.txt │ │ │└┬┘└┬┘└┬┘ │ │ │ │ │ └─ others: nothing │ │ │ │ └──── group: read │ │ │ └─────── owner: read, write │ │ └───────── type: - file, d folder │ │ │ │ r read w write x run - not allowed │ │ │ └────────────────────────────────────────────┘
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:
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.
- 01
Most servers and container images run Linux. Your Mac terminal uses the same commands.
- 02
Settings live in /etc, logs in /var/log. Everything hangs from one root,
/. - 03
Each running program is a process with a PID. Platforms stop it with SIGTERM, then SIGKILL, so handle SIGTERM.
- 04
Permissions are read, write, run for owner, group and others. Do not run containers as root.
Read more
- signal(7), the Linux manual page. The table of signals and what each one does.
- Dockerfile USER from Docker. How to set the user that runs your app.
- Self-hosting Next.js from Next.js. The end of the page covers graceful shutdown on SIGTERM.
- Amazon S3 outage summary, the story above, written by AWS.
- Docker in Action, 2nd edition, chapters 2 and 6: read 2.2 on processes and the PID namespace, and 6.3 on users.
Next, you learn how load balancers, proxies and CDNs spread traffic across many servers.