
Last week, a student in our DevOps bootcamp spent an hour trying to figure out why his deployment script failed with "Permission denied" on the server. He had written the script, he owned the file, and he could read it. The problem was that the file wasn't executable. That's the moment we realized we needed to teach Linux basics before jumping into Docker.
Linux is everywhere. Your production server runs it, your CI pipeline probably runs it, and even your Mac terminal behaves mostly the same. But most developers learn Linux by accident, catching commands here and there. This post covers three concepts that cause the most confusion in our classes: how the file system is organized, how permission bits work, and how to manage processes. Get these right, and you'll stop fighting the terminal.
The file system is a tree, not a drive
On Windows, you have C:, D:, and each drive has its own root. Linux has one root, /. Every file, every directory, every device, even a running process's state, hangs off that single tree. That’s why you see paths like /home/you/project or /var/log/syslog. There's no "E:". Once you accept this, the rest becomes easier.
When you open a terminal, you're in a "current working directory". The command pwd prints it. cd changes it. ls lists what's there. We tell students: learn these three first, because every other command builds on them. But the real skill is understanding absolute paths (start with /) versus relative paths (start from where you are). For example, cd /etc takes you to the config directory no matter where you are. cd ../.. moves up two levels. A common mistake we see every week: someone writes a script that assumes they're in a certain directory, then they run it from somewhere else and it fails. The fix is to use absolute paths in scripts or to explicitly cd at the start.
Let's look at a directory listing. Run ls -l and you'll see output like this:
-rw-r--r-- 1 alice devs 4096 Mar 12 10:33 report.txt
drwxr-xr-x 2 alice devs 4096 Mar 12 10:34 scripts
The first character tells you the type: - for a regular file, d for a directory. The next nine characters are permissions, which we'll dig into in the next section. Then comes the owner (alice), the group (devs), the size in bytes, the modification date, and the name. Notice that directories have a size too — it's the space used by the directory entry. That list is your window into the system.
Permissions are three sets of three
Permissions on a file or directory are shown as three groups of three characters: rwx (read, write, execute). The first group applies to the owner, the second to the group, and the third to everyone else. So -rw-r--r-- means the owner can read and write, the group can read, and others can read. No one can execute it.
The execute bit is the one that confuses beginners. On Linux, a file doesn't need a .exe extension. It needs the x bit set. If you write a script and try to run it with ./script.sh, you'll get "Permission denied" if it's not executable. That's exactly what happened to our student. To make a file executable, you change its permissions with chmod. The simplest way is numeric: chmod 755 script.sh. The three digits are the sum of read (4), write (2), and execute (1) for each class. So 755 means owner gets 7 (4+2+1, full), group gets 5 (4+1, read+execute), others get 5. A common setup for scripts is 755. For files that don't need to run, 644 is standard: owner read/write, others read.
You'll also see symbolic notation, like chmod u+x script.sh (add execute for the user). Both work; use what's clear to you. One thing we stress: don't use chmod 777 unless you really want everyone on the system to be able to read, write, and execute that file. It's a security hole. We've seen students do it to "make things work" and then wonder why their server got compromised.
Directories have a subtlety. The read bit lets you list the contents, the write bit lets you create or delete files inside, the execute bit lets you cd into it. So if a directory is drwxr-xr-x, the owner can do everything, but others can only read and enter. That's why you can't delete a file in a directory you don't own, even if the file itself is writable.
There's also umask, the default permission mask. When you create a new file, the system subtracts the umask from the base permissions. Most systems have a umask of 022, which means new files get 644 and new directories get 755. If you ever wonder why a freshly created file isn't executable, it's the umask. You can check yours with umask and change it for a session, but that's rarely needed.
Processes are just programs with a number
Every running program is a process, and each process has a unique PID (process ID). When your code starts, the shell runs it as a process. You can see what's running with ps. The classic command is ps aux — it shows all processes, not just your own, with user, CPU usage, memory, and the command. If something is stuck, you find its PID and kill it.
Killing sounds harsh, but it's a normal part of development. Suppose you start a development server, and it won't stop when you press Ctrl+C. You open another terminal, run ps aux | grep node, find the PID, then kill 1234. The kill command sends a termination signal. By default it sends SIGTERM, which asks the process to shut down gracefully. If that doesn't work, kill -9 sends SIGKILL, which the kernel enforces immediately. Use -9 as a last resort, because the process can't clean up its files or sockets.
You can also run a command in the background by appending &. For example, python app.py & returns the PID and gives you your prompt back. The process keeps running in the background. To bring it back to the foreground, use fg. To send it to the background, bg. And jobs lists the background jobs for your current shell. This is handy when you're testing a long-running script but still want to use the terminal.
One thing that surprises beginners: if you close your terminal, background processes might get killed too, depending on how they were started. To make a process survive a logout, you need nohup or a tool like screen or systemd. That's beyond this post, but it's worth knowing because it explains why your server process dies when you SSH out.
Pipes are the glue that combines processes. The | symbol takes the output of one command and feeds it into another. ps aux | grep python filters the process list for lines containing "python". This is a powerful pattern: you build small tools and chain them. We use it constantly in debugging. But that's a whole other topic.
Common questions
What is the difference between a shell and a terminal?
A terminal is the window you type into; it's the graphical interface that sends keystrokes to the shell and displays its output. The shell is the actual program that interprets your commands — bash, zsh, or fish. You can change your shell without changing your terminal, and you can run different shells inside the same terminal.
How do I change the owner of a file?
Use chown. For example, chown alice:devs report.txt sets the owner to alice and the group to devs. You usually need sudo for this because changing ownership is a privileged operation. If you're not sure whether you need sudo, try without it first and see what error you get.
Why does kill -9 sometimes not work?
kill -9 sends SIGKILL, which the kernel handles, so it almost always works — unless the process is in an uninterruptible sleep state (often waiting on disk I/O) or it's a zombie (already dead, waiting for its parent to reap it). You can't kill a zombie; you have to kill its parent. Check the ps state column: a Z means zombie.
Courses this relates to
Come and sit a class before you decide
Reading about it only gets you so far. Pick a day, sit in on a class that is already running, write some code, and talk to the students in it. Free, and nothing to pay afterwards unless you want to join.