TechEarl
Topic · DevOps

DevOps

Pipelines, deploys, on-call, and the quiet plumbing that keeps production alive.

199 articlesWritten by Ishan Karunaratne
DevOps practice notes on CI/CD pipelines, deploys, on-call workflows, and production reliability.
More in DevOps
Why find scripts break between macOS and Linux: -printf and -regextype are GNU only, regex flavors and stat format strings differ. The portable find subset, the gotchas, and brew install findutils for gfind.

BSD find vs GNU find: Every macOS vs Linux Difference That Matters

macOS ships BSD find; Linux ships GNU find. The two share a name and most of an interface, but -printf, -regextype, and the stat format strings diverge hard enough to break scripts shipped between platforms. The full divergence list, the portable subset that works on both, and how to get GNU find on a Mac.

The grep -o | sort | uniq -c | sort -rn pipeline counts unique matches and ranks them. Why sort comes before uniq, worked log-analysis examples, sort -u, uniq -d, and the awk one-pass alternative.

How to Count Unique Matches with grep, sort, and uniq

The grep -o 'pattern' file | sort | uniq -c | sort -rn pipeline is the classic log-analysis one-liner. Why sort must come before uniq, how each stage works, worked examples for top IPs and status codes, the awk one-pass alternative for huge files, and the BSD vs GNU notes.

find walks the live filesystem every time; locate and plocate query a prebuilt updatedb database. Compare freshness, speed, permission-awareness, and filtering, plus the mlocate vs plocate split and the macOS mdfind alternative.

find vs locate vs mlocate: Which File Search Tool to Use

find walks the live filesystem every time it runs: always current, sometimes slow. locate queries a prebuilt database: instant, but stale until the next updatedb. This breaks down the locate family (mlocate, plocate, slocate), the macOS situation, and exactly when to reach for each one.

Use grep 'pattern' file | awk '{print $2}' to filter lines and print a specific column. awk field basics, custom separators with -F, multi-column output, grep -o and cut alternatives, and when awk alone replaces the pipe.

How to grep and Print a Specific Column (grep + awk)

grep filters lines, awk extracts fields. The classic pipe is grep 'pattern' file | awk '{print $2}'. This covers awk field basics ($1, $NF), custom separators with -F, multi-column output, the cases grep -o and cut cover on their own, and the fact that awk's own pattern match makes the grep half optional.

rsync files modified in the last N days by piping find -print0 into rsync --files-from=- --from0. The relative-path gotcha, the dry run, BSD vs GNU notes, and when rsync filters replace find.

How to rsync Only the Files find Selected

rsync has no native time filter, so the standard trick is to let find pick the files and feed the list to rsync. The one-liner is find ... -print0 | rsync --files-from=- --from0, and the failure mode is always the same: the paths in the list have to be relative to the rsync source argument. The breakdown, the dry run habit, and when rsync's own filters make find unnecessary.

Archive every file matching a find pattern with tar. The safe find -print0 | tar --null --files-from=- one-liner, the macOS BSD tar -T difference, archiving by modification time, and gzip vs bzip2 vs xz vs zstd.

How to Archive Files Matching a find Pattern with tar

find locates the files, tar archives them. The safe pairing is find -print0 piped into tar reading a NUL-delimited list from stdin: no breakage on spaces or newlines. The flag breakdown, the macOS BSD tar vs GNU tar difference, the -exec append alternative, archiving by modification time, and the compression choices.

grep is universal and searches everything you point it at; ripgrep (rg) is the fast Rust default that skips .gitignore'd and binary files; ag is the older fast-grep now superseded. Compare speed, defaults, and regex engines.

grep vs ripgrep vs ag: Which Search Tool to Use

grep is on every system and searches exactly what you point it at. ripgrep (rg) is the fast Rust-based default for code search: it skips .gitignore'd, hidden, and binary files unless told otherwise. ag (the_silver_searcher) was the older fast-grep, now largely superseded by ripgrep. This breaks down speed, defaults, regex engines, and exactly when to reach for each one.

Use xargs -P to run find results in parallel: find ... -print0 | xargs -0 -P 4 -n 1 cmd. Set -P to the core count, why -n 1 matters, CPU-bound vs IO-bound work, and xargs -P vs GNU parallel.

How to Run find in Parallel with xargs -P

find . -type f -name '*.log' -print0 | xargs -0 -P 4 -n 1 gzip compresses every matched file four at a time. The flags that make it work: -P for parallel workers, -n 1 so each worker gets one job, -0 paired with find's -print0 for safety. When parallelism helps (CPU-bound work) and when it just thrashes the disk.

find -name uses shell globs on the basename; find -regex matches a full regular expression against the whole path. The -regextype flavors, the GNU emacs vs BSD basic default drift, and when each one is the right tool.

find -regex vs -name: When to Use Regex in find

find -name takes a shell glob and matches the basename; find -regex takes a full regular expression and matches the whole path. That whole-path detail is the number one surprise: -regex '.*\.txt' works but -regex '.txt' matches nothing. The flag reference, -regextype flavors, the GNU vs BSD default-flavor drift, and when -name is the better tool.

Skip node_modules, .git, and build output from grep with --exclude-dir, exclude filename globs with --exclude, and search only matching files with --include. The one-liner, brace expansion, --exclude-from, and the BSD grep fallback.

How to Exclude Files and Directories from grep

grep does not read .gitignore, so skipping node_modules, .git, and build output is on you. The flags that do it: --exclude for filename globs, --exclude-dir for whole directories, --include for the inverse, --exclude-from to read the list from a file, plus the find -prune fallback for older macOS grep.

Exclude a directory in find with -path './node_modules' -prune -o ... -print. Why the trailing -print is mandatory, the multi-directory form, the slower -not -path alternative, and BSD vs GNU notes.

How to Exclude a Directory in find (the -prune Pattern Explained)

find -path './node_modules' -prune -o -type f -print skips a directory subtree instead of walking into it. The pattern looks strange because -prune is an action, not a test, and the trailing -print is mandatory once you write an explicit action. The breakdown, the multi-directory form, the slower -not -path alternative, and when each one is the right call.

Use grep -l 'pattern' files to list only the filenames that contain a match. The inverted grep -L, the recursive grep -rl one-liner, the NUL-safe xargs pipeline for find-and-replace, and the macOS BSD vs GNU notes.

How to List Only Filenames with grep -l

grep -l prints the name of each file that contains a match and stops reading at the first hit, which makes it the fast answer to 'which files contain this string'. The lowercase -l, the inverted -L for files missing a pattern, the grep -rl one-liner, the NUL-safe xargs pipeline for find-and-replace, and the BSD vs GNU notes.

Rank the biggest files on a full disk with find -printf '%s %p' piped to sort -rn. The GNU one-liner, the BSD stat variant for macOS, why -xdev matters, human-readable sizes, and when du or ncdu beats find.

How to Find the Largest Files on Disk (find, sort, du)

find / -xdev -type f -printf '%s %p\n' | sort -rn | head -20 gives you a ranked list of the biggest files on a full disk. The GNU one-liner, the BSD/macOS stat variant, why -xdev matters, human-readable output with numfmt, when to switch to du or ncdu for per-directory totals, and the mistakes that send a scan into /proc.

grep -E vs grep -P explained: basic regex (BRE) treats + ? | ( ) { } as literal text, extended regex (ERE) makes them metacharacters, and PCRE adds lookaround and \d. Plus why macOS BSD grep has no -P.

grep Regex: BRE vs ERE vs PCRE Explained

grep has three regex engines and the default one surprises everyone: in basic regex (BRE) the characters + ? | ( ) { } are literal text until you backslash-escape them. -E switches to extended regex (ERE) where they work bare, and -P unlocks Perl-compatible regex with lookaround and \d. The full BRE vs ERE vs PCRE comparison, the same pattern in all three, and why -P does not exist on macOS.

Use find -user, -group, and -perm to locate files by ownership and mode. The -perm -mode vs -perm /mode distinction explained, world-writable and SUID/SGID audit recipes, orphaned-file checks, and the BSD vs GNU find differences on macOS.

How to Find Files by Owner, Group, or Permission with find

find -user www-data lists every file owned by a user; -group developers filters by group; -perm matches the mode bits. The subtle part is -perm -mode (all of these bits set) versus -perm /mode (any of these bits set). Plus the security-audit recipes for world-writable files and SUID/SGID binaries, the BSD vs GNU divergences, and the orphaned-file checks.

Use grep -w to match a whole word instead of a substring. What grep counts as a word boundary, the \b and \< \> regex equivalents, -x for whole-line match, and BSD vs GNU differences.

How to Match a Whole Word with grep -w

grep cat also matches category, concatenate, and scatter. grep -w cat matches only the standalone word. The whole-word flag, what grep counts as a word boundary, the regex equivalents with \b and \< \>, the stricter -x whole-line cousin, and the BSD vs GNU differences that bite on macOS.

Use find ... -print0 | xargs -0 grep -l 'PATTERN' to find every file containing a string. When grep -r is enough, when to add find as a pre-filter for performance, multi-pattern matching with -E, and the safe NUL-delimited pipeline.

How to Find Files Containing Specific Text (find + grep)

find ... -print0 | xargs -0 grep -l 'PATTERN' finds every file containing a piece of text. The combo handles weird filenames, scales to huge trees, and replaces three other common but broken pipelines. When to use grep -r alone, when to add find as a pre-filter, and the BSD vs GNU pitfalls.

Use find -type d -empty to list empty directories and find -type f -empty for empty files. The -depth trap for deleting nested empty trees, the hidden-file gotcha, the safe two-pass cleanup, and BSD vs GNU find notes.

How to Find (and Delete) Empty Directories and Files

find . -type d -empty lists every empty directory; find . -type f -empty lists every empty file. The catch is what 'empty' means (a hidden file makes a directory not empty) and the -depth trap that lets find -delete collapse whole nested empty trees in one pass. The flag reference, the safe two-pass cleanup, the BSD vs GNU notes, and the mistakes that bite.

Connect to a GCP Compute Engine VM with plain OpenSSH and no gcloud CLI. Add a public key via instance metadata, ssh to the external IP, configure ~/.ssh/config, plus OS Login and IAP.

How to SSH into a Google Cloud VM Without gcloud

Connect to a GCP VM using plain OpenSSH, no gcloud required. Add a public key to instance metadata, fetch the external IP, and ssh in like any normal Linux box. Plus OS Login, IAP, and a Windows PuTTY path.

Search multiple patterns with grep: grep -e 'A' -e 'B', grep -E 'A|B' alternation, and grep -f patterns.txt. Covers -F fixed strings, AND logic with chained greps and PCRE lookahead, and BSD vs GNU differences on macOS.

How to Search Multiple Patterns with grep

grep can OR several patterns three ways: -e per pattern, -E with alternation, or -f reading the list from a file. The one-liner is grep -E 'ERROR|WARN|FATAL' file. Here is when to pick each, how -F speeds up literal multi-pattern search, why grep has no single-pass AND, and the BSD vs GNU differences that bite on macOS.

Use grep -C 3 'pattern' file to print 3 lines before and after each match. The -A, -B, -C context flags, the -- group separator, asymmetric context, recursive search, and BSD vs GNU grep differences.

How to Show Lines Before and After a grep Match (Context)

grep -C 3 'pattern' file prints the matching line plus 3 lines on each side. The three context flags (-A after, -B before, -C both), how the -- group separator works between match blocks, asymmetric context, recursive context search, and the macOS BSD vs GNU differences that bite.

find -exec ... {} + batches arguments into one command (fast). find ... -exec ... {} \; forks per file (slow). xargs adds shell flexibility but needs -0 for safety. The decision matrix and performance comparison.

find -exec vs xargs: Which to Use (and the {} + Trick That Beats Both)

find -exec ... {} + and find -print0 | xargs -0 are roughly equivalent for batch operations on matched files. find -exec ... {} \; forks once per match and is much slower. The decision matrix: when -exec is enough, when xargs adds value, and the safety rules for filenames with spaces, newlines, and quotes.

grep -c counts matching lines, not occurrences. Use grep -o piped into wc -l for the true count, grep -rc for per-file counts, grep -vc to count non-matching lines, plus the macOS BSD vs GNU differences.

How to Count Matches with grep -c (and the Line-vs-Occurrence Trap)

grep -c counts matching LINES, not occurrences. A line with three hits still counts as 1. The fix is grep -o piped into wc -l, which puts every match on its own line first. Per-file counts, filtering out the :0 noise, counting non-matching lines, and the BSD vs GNU differences.

Use find -type f -name '*.txt' for one extension, or group -name tests in escaped parens joined by -o for many (.jpg, .png, .gif). Case-insensitive -iname, files with no extension, the -regex shortcut, and BSD vs GNU find differences.

How to Find Files by Extension (One or Many) with find

find . -type f -name '*.txt' lists every file with one extension. For many extensions you group -name tests with escaped parens and join them with -o. This covers the single one-liner, the multi-extension OR pattern, why the parens are mandatory, case-insensitive -iname, files with no extension at all, the -regex shortcut, and the BSD vs GNU divergence that bites on macOS.

find -delete removes every matched file with no confirmation. The safe -print-first dry-run pattern, depth-first directory deletion, when to use -exec rm vs xargs rm -f, and the BSD vs GNU differences.

How to Find and Delete Files Safely with find -delete

find -delete removes every matched file with no confirmation and no undo. The safe pattern is to write the command with -print first, eyeball the list, then swap -print for -delete. Plus the directory-depth-first trap, when to use -exec rm instead, and the find -delete vs xargs rm -f tradeoff.

How to grep case-insensitively with grep -i. Combine -i with -r, -w, -v, -c, the locale caveat for non-ASCII case folding, the PCRE (?i) inline flag, and BSD vs GNU grep differences.

How to grep Case-Insensitively (grep -i)

grep -i 'pattern' file matches regardless of case. The flag pairs with -r, -w, -v, and -c the way you would expect, but -i only folds ASCII case reliably. Non-ASCII case folding (accented characters, the Turkish dotted-i) depends on your locale. The combinations, the locale caveat, the PCRE per-pattern (?i) flag, and the BSD vs GNU differences.

Use find -size +100M to list files larger than 100 megabytes. Unit suffixes (c/k/M/G), +/- sign convention, combine with sort -rn to surface the biggest files on disk, and BSD vs GNU rendering differences.

How to Find Files Larger Than a Size with find -size

find . -size +100M lists every file larger than 100 megabytes. The unit suffixes (c, k, M, G), the +/- sign convention, how to combine with sort to find the biggest files on disk, the BSD vs GNU divergence for printing sizes, and the wc -c trick for byte-exact thresholds.

Use grep -v 'pattern' file to print every line that does not match. Exclude multiple patterns with -e or -vE, strip comments and blank lines, count with -vc, and avoid the OR-becomes-AND double-negative trap.

How to Exclude Matches with grep -v (Invert Match)

grep -v 'pattern' file prints every line that does NOT match. The flag reference, how to exclude multiple patterns, the strip-comments-and-blank-lines pipeline, the double-negative trap where -v of an OR becomes an AND of negations, and the macOS BSD vs GNU differences.

Use find -mtime -7 to list files modified in the last 7 days. The off-by-one (-7 means under 7 days, +7 means over 7 days), -mmin for minute resolution, -newer for exact timestamps, and the BSD rounding gotcha on macOS.

How to Find Files Modified in the Last 7 Days (find -mtime)

find -mtime -7 lists every file modified in the last 7 days. The catch is the off-by-one: -7 means less than 7 days ago, +7 means more than 7 days ago, and exact-7 almost never matches what people expect. The flag reference, worked variations for hours and minutes, the BSD vs GNU rounding difference, and the safe cleanup patterns.

Use grep -r 'pattern' . to search every file in a directory tree. The -r vs -R symlink difference, --include and --exclude-dir filters, -rl and -rn, and the macOS BSD vs GNU grep gaps.

How to grep Recursively Through a Directory

grep -r 'pattern' . searches every file under a directory tree. The catch is the path argument people forget, the -r vs -R symlink difference, and the unfiltered crawl into node_modules and .git. The flag reference, the --include and --exclude-dir filters, the macOS BSD vs GNU gaps, and when to reach for ripgrep or git grep instead.

Bash for loop reference: brace-range {1..10}, sequence (seq), array, glob, C-style, nested, parallel with xargs. Plus safe file iteration with find -print0, globbing pitfalls, and macOS Bash 3.2 vs Linux Bash 4+ differences.

Bash For Loops: Syntax, Examples, and One-Liners

Every form of the Bash for loop with working examples: brace-range, sequence-expression, array, glob, C-style, nested, and parallel. Plus the safe file-iteration patterns, common pitfalls, and macOS Bash 3.2 vs Linux Bash 4+ gotchas.

AWS IAM policy examples by use case: S3 read-only with prefix, S3 read-write with delete denied, EC2 admin scoped to a region via aws:RequestedRegion, Lambda execute and read env vars but not write, iam:PassRole for service-linked roles, MFA-required via aws:MultiFactorAuthPresent, IP-restricted via aws:SourceIp, VPC-endpoint-only via aws:SourceVpce, tag-based prod-vs-dev isolation via aws:ResourceTag, plus the anatomy of a policy document and IAM Access Analyzer for least-privilege validation.

AWS IAM Policy Examples: S3, EC2, Lambda, and Least-Privilege Patterns

A working library of AWS IAM policy examples: S3 read-only with prefix, EC2 admin scoped to a region, Lambda execute-but-not-write, MFA-required, IP-restricted, VPC-endpoint-only, tag-based prod-vs-dev isolation, and the iam:PassRole pattern. Plus the anatomy of a policy document and how Access Analyzer narrows over-permissive Resource: "*" grants.

Bash arrays reference: declaration, indexing, [@] vs [*] quoting, iteration, appending, slicing, mapfile/readarray for lines, IFS-based string splitting, plus macOS Bash 3.2 limits.

Bash Arrays: Indexed, Associative, and Iteration Patterns

Bash array reference: indexed and associative declaration, the [@] vs [*] quoting gotcha, iterating values and indexes, appending, slicing, deleting, mapfile/readarray for reading lines, and the macOS Bash 3.2 vs Linux Bash 4+ differences.

AWS S3 CLI cheat sheet: aws s3 cp local-to-S3, S3-to-local, S3-to-S3 cross-region; aws s3 sync incremental with --delete; --exclude and --include patterns; --storage-class STANDARD_IA / INTELLIGENT_TIERING / GLACIER; --sse AES256 and --sse aws:kms; --acl bucket-owner-full-control; --dryrun for safety; concurrency tuning with max_concurrent_requests and multipart_chunksize; the trailing-slash gotcha that ruins half of all aws s3 cp invocations.

AWS S3 cp and sync Cheat Sheet: Copy, Move, and Sync Files with the CLI

A scannable AWS S3 CLI reference: aws s3 cp, sync, mv, rm, ls; recursive uploads and downloads; --exclude / --include filters; storage classes (STANDARD_IA, GLACIER, INTELLIGENT_TIERING); SSE encryption (AES256, aws:kms); --dryrun safety; the trailing-slash gotcha; concurrency tuning via max_concurrent_requests and multipart_chunksize; cross-account profiles.

Change an EC2 instance type without data loss: stop the instance, run aws ec2 modify-instance-attribute, start it again. Covers Nitro vs Xen compatibility, ENA and NVMe driver requirements, the instance-store ephemeral-data trap, and the zero-downtime ASG rolling-replace pattern.

How to Change an AWS EC2 Instance Type (Resize Without Data Loss)

Stop the instance, modify the instance type, start it. The exact gcloud-equivalent AWS CLI syntax, the compatibility matrix for moving between families and generations, the Nitro vs Xen gotcha, the instance-store data-loss trap, and the production sequence (snapshot AMI, scale out, replace) that gets you to a new instance type with zero downtime.

Stop running Docker containers as root. Add a non-root USER to your Dockerfile, fix file ownership for bind mounts and volumes, bind privileged ports, and enforce it at runtime.

Running Docker Containers as a Non-Root User

By default, processes inside Docker containers run as root, which is risky. Switch to a non-root USER, fix permissions on volumes and ports, and configure Compose and Kubernetes to refuse to run root containers.

A working Dockerfile for PHP and Laravel: multi-stage with Composer, php-fpm + Nginx via Compose, OPcache config, and the Laravel storage / bootstrap-cache permission fix.

How to Dockerize a PHP / Laravel App

A Dockerfile for PHP-FPM + Nginx, the Composer build step, OPcache config, and the Laravel storage/cache permission flip that catches every PHP developer once.

Grow an AWS EBS volume with zero downtime: aws ec2 modify-volume to enlarge, wait for the optimizing state, then sudo growpart to extend the partition and sudo resize2fs (ext4) or sudo xfs_growfs (XFS) to stretch the filesystem. No detach, no reboot, on a live EC2 instance.

How to Extend an AWS EBS Volume Without a Restart

Grow an EBS volume on a running EC2 instance in four steps. Modify the volume, wait for the optimizing state, expand the partition with growpart, then stretch the filesystem with resize2fs or xfs_growfs. No detach, no reboot.

Create an EBS volume with aws ec2 create-volume, attach it to a running EC2 instance, format with mkfs.ext4 or mkfs.xfs, mount it, and persist across reboots with a UUID-based /etc/fstab entry. Console, AWS CLI, and Terraform walkthroughs.

How to Add an EBS Volume to an EC2 Instance

Create an EBS volume, attach it to a running EC2 instance, format and mount it, and survive reboots with a UUID-based fstab entry. Console, AWS CLI, and Terraform walkthroughs plus the Nitro device-naming gotcha that trips everyone.

A working Dockerfile for a Node.js app: multi-stage build, npm ci, non-root user, .dockerignore, the node_modules volume trick for fast Mac dev, and a Compose snippet.

How to Dockerize a Node.js App

A Dockerfile for a real Node.js app: multi-stage build, npm ci for deterministic dependencies, the node_modules-volume trick that makes bind-mounted source fast on Mac, and the non-root user that most tutorials skip.

Connect to an AWS EC2 instance using plain SSH with a key pair, EC2 Instance Connect, AWS Systems Manager Session Manager, or an EC2 Instance Connect Endpoint for private instances. Default usernames, security group rules, and troubleshooting Permission denied and Connection timed out.

How to SSH into an AWS EC2 Instance

Connect to an EC2 instance four ways: plain SSH with a key pair, EC2 Instance Connect, Session Manager, and EC2 Instance Connect Endpoint. Default usernames, security group rules, and the troubleshooting matrix that fixes Permission denied and Connection timed out.

The .dockerignore patterns I use: what to exclude, the gotchas (negation, leading slashes), why it matters for build speed, image size, and security.

.dockerignore Best Practices

The .dockerignore file controls what COPY . . actually ships into your image. Without it, your image gets node_modules, .git, .env, and every other thing in the project. With it, builds are faster, images smaller, and secrets stay out.

Restart policies (always, unless-stopped, on-failure), HEALTHCHECK in Dockerfile and Compose, and depends_on: condition: service_healthy to wait until the database is actually ready.

Docker Restart Policies and Health Checks

Make containers come back automatically after crashes and reboots, and tell Compose how to wait until a service is actually ready (not just started). Restart policies, HEALTHCHECK, and depends_on: condition: service_healthy.

Reclaim disk used by Docker: prune containers, images, volumes, networks, and the BuildKit cache. Plus what each flag actually deletes and how to avoid wiping data you wanted to keep.

docker system prune: Free Disk Space Used by Docker

Docker fills up your disk. The prune commands clean it: docker system prune for the everyday sweep, docker system prune -a --volumes for the nuclear option, docker builder prune for the BuildKit cache, plus the per-resource versions.

Run Apache httpd in Docker: serve static files, mount a custom httpd.conf, enable mod_rewrite, get .htaccess working, and pair with PHP via the bitnami or PHP-Apache images.

How to Run Apache in Docker

Apache httpd in a Docker container: serve static files, mount a custom httpd.conf, enable mod_rewrite for .htaccess, and the patterns that come up most often (PHP, reverse proxy, virtual hosts).

Run Nginx in a Docker container: serve static files, mount a custom nginx.conf, run as non-root, and use it as a reverse proxy in front of an app.

How to Run Nginx in Docker

Serve static files, mount a custom nginx.conf, run as non-root, and the practical patterns: dev server, reverse proxy in front of an app, and the SSL gotcha most tutorials skip.

View Docker container output with docker logs. Covers --follow, --tail, --since, --timestamps, log rotation, and what to do when the app writes to a file instead of stdout.

docker logs: View Container Output and Tail Logs

Read the stdout and stderr of a running or stopped container. Follow live output, tail the last N lines, filter by time, prepend timestamps, and the cases where docker logs doesn't help because the app writes to a file instead.

Use docker exec to shell into a running container or run a one-off command. Covers -it, alpine sh vs bash, --user for non-root images, and the working-directory and env flags.

docker exec: Run Commands Inside a Running Container

Shell into a running container, run one-off commands, drop down to root when you need to install something, and the difference between an interactive session and a single command. With the alpine sh vs bash gotcha and -u for breaking out of non-root containers.

A working Docker cheat sheet: images, container lifecycle, run flags, exec, logs, build, networks, volumes, Compose, registry, and prune commands. With swappable variables.

Docker Cheat Sheet

The Docker commands I actually use, grouped by job: images, container lifecycle, run flags, exec and logs, build, networks, volumes, Compose, registry, and the prune/inspect commands for keeping a host clean.

Using regex in Nginx with location blocks and the rewrite directive: location modifier priority, the rewrite directive flags, return-based redirects, and copy-paste config for HTTPS redirects, www normalization, trailing slashes, 301 redirects, clean URLs, and blocking by user-agent or IP.

How to Use Regex in Nginx (location and rewrite)

Use regex in Nginx with location blocks and the rewrite directive: how location modifiers and matching priority work, why return beats rewrite for redirects, and copy-paste config for HTTPS, www, trailing slashes, 301s, clean URLs, and access blocking.

Using regex in Apache .htaccess with mod_rewrite: RewriteRule and RewriteCond pattern syntax, rewrite flags, and copy-paste rules for HTTPS redirects, www normalization, trailing slashes, 301 redirects, clean URLs, and blocking by user-agent or IP.

How to Use Regex in .htaccess (Apache mod_rewrite)

Use regex in .htaccess with Apache mod_rewrite: how RewriteRule and RewriteCond patterns work, the per-directory quirk that breaks everyone, and copy-paste rules for HTTPS, www, trailing slashes, 301s, clean URLs, and access blocking.

duf is a df replacement that prints grouped, color-coded disk usage tables. Install it, filter local devices with --only local, sort by --sort usage, hide mounts with --hide-mp, and emit --json for scripts.

duf: A Friendlier df for Disk Usage

duf is a df replacement that prints grouped, color-coded disk usage tables instead of df's raw columns. Install it, filter to the devices you care about, sort by usage, and pipe --json into scripts.

nvm vs fnm vs Volta comparison: which Node.js version manager to choose by speed, auto-switching, and per-project pinning

nvm vs fnm vs Volta: Which Node Version Manager?

nvm vs fnm vs Volta, compared by speed, auto-switching, platform support, and pinning model. Which Node version manager to pick in 2026, with install commands, the .nvmrc vs package.json question, and honest caveats from running all three.

How to create a pull request on GitHub and GitLab: the branch, push, open, review, and merge flow for beginners.

How to Create a Pull Request

A pull request proposes merging one branch into another so a teammate can review it first. Here is the full flow on GitHub and GitLab: branch, push, open, review, merge.

How to discard local changes in Git with git restore, git checkout, and git clean to reset your working tree to the last commit.

How to Discard Local Changes in Git

Discard local changes in Git with restore, checkout, and clean. How to throw away edits to tracked files, delete untracked files, and reset to the last commit.

How to set up push-to-deploy with GitHub Actions: a CI/CD workflow that builds and deploys on every push to main.

Push-to-Deploy with GitHub Actions

A beginner-friendly intro to CI/CD with GitHub Actions: a real workflow YAML that builds and deploys on every push to main, plus how to handle secrets safely.

How to remove a leaked secret like an API key or password from Git history using git filter-repo or BFG.

How to Remove a Secret from Git History

Committed an API key or password? Deleting it in a new commit does not remove it. Rotate the credential first, then scrub it from all history with git filter-repo or BFG.

How to change your GitHub profile picture from the default identicon to a custom avatar in Settings

How to Change Your GitHub Profile Picture

Swap GitHub's default identicon for a real avatar in under a minute: where the setting lives, the crop step, the size and format that work, and the gotcha that trips people up.

Git commit message best practices: the 50/72 rule, imperative mood, and Conventional Commits explained for beginners

Git Commit Message Best Practices

A good Git commit message uses a short imperative subject under 50 characters, a blank line, then a wrapped body that explains why. Here is the whole convention.

How to register a custom WP-CLI command in WordPress: a command class wired up with WP_CLI::add_command(), public methods as subcommands, args and flags documented with @synopsis, and a make_progress_bar() loop for batch jobs.

Add a Custom WP-CLI Command in WordPress

How to register a custom WP-CLI command: guard it with defined('WP_CLI'), wire it up with WP_CLI::add_command(), turn class methods into subcommands, document args with @synopsis, and show progress with make_progress_bar().

Git branching explained for beginners, covering branches, HEAD, git checkout, git switch, and how to create, list, rename, and delete branches.

Git Branching Explained for Beginners

What a Git branch actually is, how HEAD points at your current spot, and the commands to create, switch, list, rename, and delete branches with confidence.

Set up time-saving git aliases with git config: st for status, lg for a graph log, unstage, amend, and undo, plus the ! shell-command form and shell-level aliases.

Git Aliases That Actually Save Time

A curated set of git aliases worth keeping: st, lg, last, unstage, amend, undo. How to set them with git config, where they live in ~/.gitconfig, and when you need a shell alias (the ! prefix) instead of a plain one.

Run scripts automatically on commit, push, and checkout with git hooks: the native .git/hooks files, sharing them with core.hooksPath, and the husky v9 setup.

Git Hooks: Run Scripts on Commit, Push, and Checkout

Git hooks run your scripts automatically on commit, push, and checkout. Where the native hooks live, the common ones, why .git/hooks is not shared, how core.hooksPath fixes that, and the husky v9 setup for JS projects.

Using Git with a WordPress project: what to track, what to ignore, and how to deploy.

How to Use Git with WordPress

How to put a WordPress project under Git: what to track vs ignore, a ready .gitignore, version-controlling just your theme or plugin, and deploy options.

nvtop gpu monitoring on Linux: an htop-style ncurses viewer for NVIDIA, AMD, and Intel GPUs showing live utilization, VRAM, temperature, and a sortable per-process list. Install, key bindings, and -d delay flag.

nvtop: Monitor NVIDIA, AMD, and Intel GPUs on Linux

nvtop is an htop-style GPU monitor for Linux. One install (sudo apt install nvtop), one command, and you get live per-GPU utilization, memory, temperature, and a sortable per-process list across NVIDIA, AMD, and Intel cards.

List the files changed in git from the command line: working tree, staged, last commit, between two branches, and piping the list to a linter to check only what changed.

How to List the Files Changed in Git

List the files changed in git: working tree, staged, the last commit, between branches, or since N commits. Then pipe the list straight into a linter so you only check what changed.

bat is a cat command replacement with syntax highlighting, line numbers, and Git change markers. Install it, alias cat with --paging=never, and use --plain, --style, --paging, and --diff in scripts and pipes.

bat: A cat Clone With Syntax Highlighting

bat is a cat command replacement that adds syntax highlighting, line numbers, and Git change markers. Install it, alias cat to it carefully, and know the --plain, --style, --paging, and --diff flags that make it behave well in scripts and pipes.

How to fix the NODE_MODULE_VERSION mismatch error in Node.js by rebuilding native modules against the current ABI

Fix NODE_MODULE_VERSION Mismatch in Node.js

The NODE_MODULE_VERSION mismatch error means a native addon was compiled against a different Node ABI than the one now running it. Here is what the numbers mean, the one-line fix, and the Electron, Docker, and CI variants that catch people out.

How to sync a fork with the upstream repository in Git by adding an upstream remote and fetching

How to Sync a Fork with the Upstream Repo

Your fork falls behind the original project the moment someone else merges a change. Add an upstream remote, fetch it, then merge or rebase to catch up before you open a PR.

Create, push, list, and delete git tags: annotated vs lightweight tags, pushing tags to a remote, and removing a tag locally and on origin.

Git Tags: Create, Push, List, and Delete

Create, push, list, and delete git tags from the command line. When to use an annotated tag over a lightweight one, why git push leaves your tags behind, and how to delete a tag on the remote.

exa and eza are modern ls replacements for the terminal: colorized output, a built-in git status column, tree view with -T, icons, and a long-listing that beats ls -l. eza is the maintained fork to install now that exa is unmaintained.

exa and eza: A Modern ls Replacement

exa was the modern ls replacement with color, a git column, and a tree view in one binary; it is now unmaintained and eza is the drop-in fork to install instead. The flags, the eza migration, and when plain ls is still the right call.

Enable TCP BBR congestion control on Linux with net.ipv4.tcp_congestion_control=bbr and net.core.default_qdisc=fq. The kernel version that ships BBR, the fq qdisc it needs, throughput gains on lossy links, and BBR vs CUBIC.

Speed Up a Linux Server With TCP BBR

Switch to TCP BBR congestion control with two sysctl lines and a reboot-safe config file. The throughput it buys you on lossy long-haul links, the fq qdisc it needs, the kernel version that ships it, and when CUBIC is still the right default.

How to uninstall Node.js completely on Linux, macOS, and Windows, including removing leftover npm, npx, and global package directories

How to Uninstall Node.js (Every Install Method)

How to uninstall Node.js cleanly on Linux, macOS, and Windows: version managers (nvm, fnm, Volta, n), the official installer, apt/dnf/brew/winget/choco, Docker, plus the leftover npm/cache/PATH files everyone forgets to delete.

Squash Git commits into one with interactive rebase: change pick to squash or fixup, fold commits into the line above, or flatten a branch with git merge --squash.

How to Squash Git Commits (Interactive Rebase and Friends)

Squash Git commits into one with interactive rebase: change pick to squash or fixup, fold into the line above, abort cleanly, or flatten a whole branch with git merge --squash. The whole workflow, including the force-push step you cannot skip.

How to set up Git for a new project: git init, a starter .gitignore, the first commit, and pushing to a new remote

How to Set Up Git for a New Project

Set up Git for a new project the right way: git init, a starter .gitignore, your first commit, creating the remote, pushing, and turning on branch protection.

ShellCheck tutorial: install the shell script linter, run it on a Bash file, read SC2086 and other SC codes, suppress findings with disable directives, and add it to CI.

ShellCheck: Catch Bash Bugs Before They Bite

ShellCheck is a static analysis linter for Bash and POSIX sh: install it, run it on a script, read the SC codes, suppress false positives with disable directives, and wire it into CI.

How to resolve merge conflicts in Git by editing conflict markers and committing the result

How to Resolve Merge Conflicts in Git

A merge conflict means Git found two changes to the same lines and needs you to pick. Here is how to read the conflict markers, fix them by hand or with a mergetool, and commit.

Set up a global gitignore with core.excludesFile so .DS_Store, .vscode, .idea, and swap files are ignored across every repo, and verify it actually works.

How to Set Up a Global .gitignore

Set up a global .gitignore so .DS_Store, editor folders, and swap files stay out of every repo you touch, without editing each project's .gitignore. The one path mistake that makes it silently do nothing, and how to verify it.

Undo anything in git by situation: restore unstaged changes, unstage a file, undo a commit, revert a pushed commit, clean untracked files, and recover with reflog.

How to Undo (Almost) Anything in Git

A by-situation map for undoing things in git: discard a change, unstage a file, undo a commit, undo a pushed commit safely, delete untracked files, and recover with reflog. Modern restore and switch, not just checkout and reset.

How to schedule a cron job on Linux: edit your crontab, write the five-field cron schedule, and point it at a command to run on a recurring timer.

How to Schedule a Cron Job on Linux

Schedule a recurring task on Linux with cron: open your crontab, write the five-field schedule, point it at a command, and avoid the environment and day-of-week traps that make jobs run at the wrong time.

Why SSH rejects your key with Permission denied (publickey) and the ordered checklist that fixes it: agent, identity file, server authorized_keys, and permissions.

Fix SSH "Permission denied (publickey)"

The ordered checklist for SSH Permission denied (publickey): is the key loaded, is it the right key, is the public half on the server, and are the server-side permissions sane.

Bind separate SSH keys to separate hosts in ~/.ssh/config with IdentityFile and IdentitiesOnly so the correct key is offered every time.

Use Multiple SSH Keys with ~/.ssh/config

Run separate SSH keys for work, personal, and GitHub by binding each to its host in ~/.ssh/config with IdentityFile and IdentitiesOnly, so the right key is always offered.

How to use git stash to save uncommitted changes, then pop, apply, list, and drop them, including stashing untracked files and partial stashes.

How to Use git stash

How to use git stash to set work aside without committing. Save, list, pop, apply, and drop stashes, stash untracked files, do a partial stash, and switch branches cleanly.

Stand up a v3 .onion hidden service with Tor. HiddenServiceDir, HiddenServicePort, key backup, permissions, onion-location, single-onion mode, and operational gotchas.

Host a v3 .onion Hidden Service with Tor

End-to-end setup for a v3 .onion hidden service — torrc HiddenServiceDir and HiddenServicePort, key backup, permissions, onion-location header, single-onion mode, and the operational mistakes that get addresses leaked or lost.

which vs type vs command -v compared: which is a non-standard external program, command -v is the POSIX way to resolve a command path in scripts, and type reports aliases, functions, and builtins.

which vs type vs command -v: Find a Command Path

which vs type vs command -v for finding where a command lives: which is an external program with portability problems, command -v is the POSIX-standard choice for scripts, and type tells you the most.

Route curl, Python requests, and Node fetch through Tor's SOCKS5 proxy. Avoid DNS leaks with socks5-hostname and socks5h://. Working examples and verification.

Use Tor as a SOCKS5 Proxy with curl, Python, and Node

Route a single command, script, or HTTP client through Tor's SOCKS5 proxy — curl with --socks5-hostname, Python requests with socks5h://, Node with socks-proxy-agent — and avoid the DNS leak that catches everyone first time.

How to undo the last Git commit using amend, reset, and revert

How to Undo the Last Git Commit

Undo your last Git commit without losing work. When to use amend, reset --soft, reset --mixed, reset --hard, and revert, plus the rule for commits you already pushed.

How to use a .gitignore file to stop Git from tracking build output, dependencies, and secrets

How to Use .gitignore (with Examples)

A practical guide to .gitignore: pattern syntax, per-repo vs global ignore, ready-made templates, and the gotcha that trips everyone up - already-tracked files keep showing up.

Bash job control reference: Ctrl-Z to suspend, fg and bg to resume, jobs to list, kill %1 by job spec, and nohup, disown, and setsid to survive logout.

Bash Job Control: fg, bg, jobs, and nohup

Bash job control reference: suspend with Ctrl-Z, resume in foreground with fg or background with bg, list with jobs, target jobs by %1/%+/%-, and keep a process alive past logout with nohup, disown, or setsid.

Generate an SSH key with ssh-keygen: pick Ed25519 over RSA, decide on a passphrase, and set the ~/.ssh permissions SSH needs. Linux, macOS and Windows.

How to Create an SSH Key in 2026

Create an SSH key in one ssh-keygen command. Which key type to pick in 2026 (Ed25519 vs RSA), whether to set a passphrase, and the file permissions SSH needs, on Linux, macOS and Windows.

Understanding the Git staging area and what git add does to the index before a commit

The Git Staging Area Explained

The Git staging area (the index) is the in-between layer where you assemble exactly what goes into your next commit. Here is what git add really does, and why it exists.

List all Linux users and groups from /etc/passwd and /etc/group with getent, and tell human accounts from system accounts by UID.

How to List Users and Groups on Linux

List every user and group from /etc/passwd and /etc/group with getent, tell human accounts from system ones by UID, and see which groups a user belongs to.

Terminal output of node --version and npm --version showing the installed Node.js and npm release numbers on the command line

How to Check Your Node.js and npm Version

Run node --version and npm --version to see what you have installed. This covers every way to check Node and npm, finding which install is on PATH, reading the version inside a script, and the gotchas with version managers and multiple installs.