Skip to main content

Linux Fundamentals

Volume 5 — Platform Engineering

The Bash Reference chapter (Volume 1) covered the shell — the commands you type. This chapter is one level down: the operating system those commands run on. Every App Service Linux container, every Docker base image, and any Azure VM you'd manage all sit on some Linux distribution, and understanding the OS itself explains behavior the shell commands alone don't.

1. What "Linux" Actually Means

Strictly, "Linux" refers only to the kernel — the core program managing hardware, processes, and memory. A distribution ("distro") bundles that kernel with a package manager, standard utilities, and often a specific philosophy about how the system should be organized.

Distribution familyPackage managerCommon in
Debian / UbuntuaptThe base for most .NET/Node Docker images (mcr.microsoft.com/dotnet/aspnet is Debian-based by default)
AlpineapkMinimal container images — much smaller, but uses musl instead of glibc, which occasionally causes subtle compatibility issues with compiled dependencies
RHEL / CentOS / Fedorayum / dnfCommon in enterprise on-prem environments, less common in container images

Why this matters practically: a Dockerfile built on a Debian-based image and one built on Alpine can behave differently for the exact same application — different default shell (bash vs. ash), different available packages, and Alpine's musl libc has caused real, hard-to-diagnose issues for some compiled native dependencies that assume glibc. If a container behaves differently locally (OrbStack) vs. in Azure App Service, confirming both are actually using the same base image is a reasonable first check.

2. The Filesystem Hierarchy Standard (FHS)

Unlike Windows, Linux has one unified directory tree starting at /, with a conventional (not strictly enforced) structure most distributions follow:

PathPurpose
/etcSystem-wide configuration files
/varVariable data — logs (/var/log), caches, databases
/homeUser home directories
/usrUser-installed programs and their data
/usr/bin, /usr/local/binExecutable programs
/tmpTemporary files, often cleared on reboot
/optOptional/third-party software, often self-contained installs
/procA virtual filesystem exposing live kernel/process information, not real files on disk

/var/log is the single most important entry in this table for day-to-day debugging — application logs, system logs, and service logs conventionally live here (or are aggregated from here), and it's the first place to look on a Linux host or VM before assuming an application-level logging tool is the only source of truth.

3. Users, Groups, and Permissions at the OS Level

The Bash Reference chapter covered chmod mechanically; this is the model underneath it. Every file has exactly one owning user and one owning group, and permission checks happen in a fixed order: owner, then group, then everyone else — the first category that matches is the one whose permission bits apply, so being in the matching group can grant less access than the owner has, but a user is never checked against multiple categories at once.

whoami # current user
id # current user's UID, GID, and group memberships
cat /etc/passwd # list of all users on the system (or a summary, depending on config)
groups username # which groups a specific user belongs to

Root is the Linux superuser — UID 0, unrestricted by normal permission checks. sudo grants a normal user temporary root-equivalent access for a specific command, governed by rules in /etc/sudoers (edited with visudo, never directly, to avoid syntax errors locking out sudo entirely).

4. Processes and Init Systems

Every running program is a process, with a parent-child relationship tracing back to process ID 1 — the init system, the very first process the kernel starts, responsible for starting everything else.

systemd is the init system used by most modern distributions (Debian, Ubuntu, RHEL, Fedora) — it manages services, their startup order and dependencies, and their logs.

systemctl status nginx # check a service's current status
systemctl start nginx # start it
systemctl enable nginx # start it automatically on every future boot
journalctl -u nginx -f # follow that service's logs live

(This is covered in depth, with the full command set and a real unit file example, in the Linux Administration chapter.)

5. Package Managers

A package manager installs software along with its dependencies, and tracks what's installed for clean removal/upgrades later — the OS-level equivalent of npm/NuGet for applications.

# Debian/Ubuntu
apt update && apt install -y curl

# Alpine
apk add --no-cache curl

apt update refreshes the local package index from remote repositories — it does not itself install or upgrade anything; apt upgrade (separately) actually applies available updates. This two-step split is a common point of confusion for anyone used to a single combined command.

6. Environment Differences That Actually Bite

DifferenceWhy it matters
glibc vs. musl (Debian/Ubuntu vs. Alpine)Some compiled native dependencies assume glibc and fail or behave subtly differently under musl
Default shell (bash vs. ash/sh)A script relying on Bash-specific syntax can fail on Alpine's default minimal shell — #!/bin/sh and #!/bin/bash are not interchangeable if the script uses Bash-only features
Case sensitivityLinux filesystems are case-sensitive by default (File.txtfile.txt) — unlike macOS's default filesystem and Windows entirely, which is exactly why a project that "works fine" on a developer's Mac can fail in a Linux-based App Service or pipeline over an inconsistent import/file-reference casing
Line endings (LF vs CRLF)Windows-authored files can carry CRLF line endings that cause #!/bin/bash scripts to fail on Linux with a cryptic bad interpreter error — see the troubleshooting table below

7. Troubleshooting Playbook

SymptomLikely causeWhat to check
Import/file reference works on macOS, fails on Linux (App Service, pipeline)Case-sensitivity mismatch — macOS's default filesystem is case-insensitive, Linux is notConfirm every import/reference matches the actual file name's case exactly
bad interpreter: /bin/bash^M: no such file or directoryScript has Windows-style CRLF line endingsConvert to Unix LF line endings (dos2unix scriptname.sh, or configure the editor/Git to use LF)
Script works with bash script.sh, fails with ./script.sh on some systemsShebang line missing, wrong, or the script isn't executableConfirm #!/bin/bash is the first line and chmod +x has been applied
Package install fails, "unable to locate package"Local package index is staleapt update before apt install, not just the install command alone
Container behaves differently than expected between environmentsDifferent base images (Debian vs. Alpine) between two supposedly equivalent Dockerfiles/environmentsConfirm both actually use the same base image tag, not just a similar one
Log content that should exist is missingLooking in the wrong location, or the application logs somewhere other than /var/logCheck the application's actual configured log destination — not every app writes to the FHS-conventional location

8. Quick Reference

CategoryItemDetail
ConceptKernel vs. distributionLinux is the kernel; a distro bundles it with tools and a package manager
Filesystem/var/logConventional location for system and application logs
Filesystem/etcSystem-wide configuration
UserssudoTemporary root-equivalent access for a specific command
UsersvisudoThe only safe way to edit sudo rules
InitsystemdThe modern init system managing services on most distros
Initsystemctl / journalctlManage and inspect services and their logs
Package managerapt (Debian/Ubuntu)apt update && apt install
Package managerapk (Alpine)apk add --no-cache
Gotchaglibc vs. muslAlpine's musl can break glibc-assuming compiled dependencies
GotchaCase sensitivityLinux is case-sensitive; macOS default filesystem is not

Part of the Traxs Engineering Handbook — Volume 5: Platform Engineering. Companion chapters in this volume: Linux Administration, Kubernetes Fundamentals, CI/CD, Observability, Secrets Management.