<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexey Cherednichenko</title>
    <description>The latest articles on DEV Community by Alexey Cherednichenko (@alaxay8).</description>
    <link>https://dev.to/alaxay8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3704114%2Fb4d56382-6366-4f35-918e-371a9a16952f.jpeg</url>
      <title>DEV Community: Alexey Cherednichenko</title>
      <link>https://dev.to/alaxay8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alaxay8"/>
    <language>en</language>
    <item>
      <title>Vim for DevOps: Practical Editing Techniques for Remote Operations</title>
      <dc:creator>Alexey Cherednichenko</dc:creator>
      <pubDate>Fri, 23 Jan 2026 20:35:15 +0000</pubDate>
      <link>https://dev.to/alaxay8/vim-for-devops-practical-editing-techniques-for-remote-operations-50fm</link>
      <guid>https://dev.to/alaxay8/vim-for-devops-practical-editing-techniques-for-remote-operations-50fm</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction: Why Vim matters for remote ops
&lt;/h2&gt;

&lt;p&gt;A lot of junior engineers take one look at Vim and think, “Why on earth would I bother with this?” It’s old, clunky, and, let’s be honest, it feels about as fresh as a rotary phone. You’ve got VS Code, remote everything, and you just click your way through. So what’s the point of learning Vim?&lt;/p&gt;

&lt;p&gt;But then, you land in &lt;strong&gt;incident response or DevOps environments&lt;/strong&gt;. Suddenly, the fancy GUI is gone. You’re dropped into a terminal—no mouse, no bells and whistles—and you’ve got to move fast.&lt;/p&gt;

&lt;p&gt;At that point, Vim becomes a practical tool rather than a legacy editor. Here’s why it matters in infrastructure work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High Latency is Brutal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Imagine: you SSH into a server on the other side of the planet, or maybe the machine’s just crawling. Every time you touch your mouse in VS Code, the lag just slaps you in the face. It’s painful.&lt;/p&gt;

&lt;p&gt;Vim is terminal-native and lightweight. It transfers minimal data and avoids heavy UI redraws. There’s barely any data flying back and forth. You can keep working, even on the slowest connection, while other editors just freeze up. Editing a config file doesn’t have to be torture.&lt;/p&gt;

&lt;p&gt;If you've ever tried to delete a single line over a 300ms satellite link or a jittery VPN, you know the pain of watching your cursor 'rubber-banding' across the screen. While VS Code is busy sending telemetry and waiting for a UI redraw, Vim is already done with the edit. Vim remains responsive over high latency because it’s terminal-native and avoids heavy UI redraws.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vim is Everywhere&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s say you jump into a brand new Docker container running Alpine, poking around to fix something. Or maybe you’re dealing with some ancient server that hasn’t seen love in years.&lt;/p&gt;

&lt;p&gt;Your customized VS Code? Nowhere to be found. Heck, even nano might be missing to save a couple kilobytes.&lt;/p&gt;

&lt;p&gt;But vi or vim? Always there. If you know Vim, you can work anywhere—on a smart lightbulb, a massive EC2 server, you name it. You’re always ready.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maintaining Control During Critical Outages&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When downtime triggers a flood of alerts and every configuration mistake carries a high price.&lt;/p&gt;

&lt;p&gt;In a regular editor, it’s easy to hit the wrong key or nudge the mouse and mess something up without even noticing.&lt;/p&gt;

&lt;p&gt;Vim breaks things up—Normal Mode for moving and reading, Insert Mode for typing. It’s like it forces you to slow down, think, and avoid stupid mistakes. When the pressure’s on, that focus matters.&lt;/p&gt;

&lt;p&gt;For incident and operations work, Vim is a reliable fallback when GUI tools are unavailable. Sure, most days you’ll use your IDE. But when the GUI is no longer an option, you’re the one who maintains control.&lt;/p&gt;

&lt;p&gt;Editing as Text Operations&lt;/p&gt;

&lt;p&gt;A lot of newbies treat Vim like Notepad. They jump into Insert Mode and poke around with the arrow keys. That’s missing the point.&lt;/p&gt;

&lt;p&gt;Vim isn’t just another text editor. It’s more like a language.&lt;/p&gt;

&lt;p&gt;In Normal Mode, you’re not just pecking at keys - you perform structured text operations rather than character-by-character edits. You’re not backspacing through mistakes, you’re making precise moves. Vim commands always follow a pattern: Verb plus Noun, or Action plus Object. That’s how you get fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning path overview
&lt;/h3&gt;

&lt;p&gt;Honestly, most people give up on Vim because it feels like getting tossed into a pitch-black room and someone’s hidden the door. You get stuck, panic a little, and run back to whatever editor feels safe.&lt;/p&gt;

&lt;p&gt;But if you’re an operations engineer, you don’t need to write your own Vim plugins or become some command-line wizard. Normal Mode is optimized for navigation and editing commands. Use it as the default state; switch to Insert Mode only when typing text. So here’s the plan: in the next ten minutes, we’ll close that gap. Here’s how it breaks down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Foundation: Set up Vim so it actually feels modern, not like you’re typing on some relic from the ‘70s.&lt;/li&gt;
&lt;li&gt;The Navigation: Figure out how to jump around a giant log file in seconds—no endless scrolling.&lt;/li&gt;
&lt;li&gt;The Surgery: Get really good at the handful of commands that save your skin when you have to edit important configs like &lt;code&gt;nginx.conf&lt;/code&gt; or &lt;code&gt;docker-compose.yaml&lt;/code&gt;, and the clock’s ticking.&lt;/li&gt;
&lt;li&gt;The Automation: Unlock some of Vim’s hidden tricks to blast through repetitive edits—stuff that takes forever in a GUI but only seconds here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this, you won’t just know the infamous exit command (&lt;code&gt;:q!&lt;/code&gt;—there you go). You’ll be able to jump onto any server, anywhere, and fix critical bugs faster than your teammates can even launch their IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understanding the Logic: The Three Pillars of Vim
&lt;/h2&gt;

&lt;p&gt;If you want to get good at Vim, you have to wrap your head around one core idea: Vim isn’t like most editors. Most editors just let you type and that’s it. But Vim works in modes. It assumes you spend way more time bouncing around, editing, and fine-tuning code than cranking out long paragraphs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Normal Mode: Command mode
&lt;/h3&gt;

&lt;p&gt;In other editors, “Normal” just means you’re typing. In Vim, Normal Mode is command central. It’s not a pause. Imagine you’re flying a plane. You’re not always gripping the stick—sometimes you’re flipping switches or checking gauges, steering without ever “writing.” When you’re in Normal Mode, every key is a shortcut. No more Ctrl+Shift+Whatever. Want to delete? Just tap ‘d’. Need to find something? Hit ‘f’. It feels like flight mode—smooth, fast, a little dangerous. You should be in Normal Mode almost all the time. If you’re not actively typing, just hit Esc and you’re back in the cockpit.&lt;/p&gt;

&lt;p&gt;Insert Mode: The Workstation&lt;/p&gt;

&lt;p&gt;Insert Mode is the only part that feels like a regular editor. Press ‘i’ and you can type. Simple. But here’s the catch: beginners get stuck here and start using arrow keys to move around. Don’t fall for it. Avoid navigating in Insert Mode; return to Normal Mode for movement. Just drop in, type what you need, and hit Esc to pop back out.&lt;/p&gt;

&lt;p&gt;Visual Mode: The Highlighter&lt;/p&gt;

&lt;p&gt;You get here with ‘v’ (for characters), ‘V’ (for lines), or Ctrl+v (for blocks). Visual Block mode is a lifesaver, especially for DevOps folks. For tasks like mass-commenting YAML or re-indenting code blocks, Visual Block mode provides the ability to edit across multiple lines simultaneously. This vertical precision is far more efficient than handling lines one by one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Grammar of Vim: Thinking in Sentences
&lt;/h3&gt;

&lt;p&gt;If you try to memorize every Vim command as a unique shortcut, you will fail. Instead, you need to understand the &lt;strong&gt;Vim Grammar&lt;/strong&gt;. Vim operates on a simple syntax: &lt;strong&gt;[Number] + [Verb] + [Noun]&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is literally a language where you tell the editor what to do, how many times, and to what object.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Verbs (What to do)
&lt;/h4&gt;

&lt;p&gt;These are your primary actions. Once you learn a verb, you can apply it to any object.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;d&lt;/code&gt; — &lt;strong&gt;Delete&lt;/strong&gt;: Removes text (and saves it to the clipboard).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c&lt;/code&gt; — &lt;strong&gt;Change&lt;/strong&gt;: Deletes text and puts you in &lt;strong&gt;Insert Mode&lt;/strong&gt; immediately (perfect for fixing mistakes).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;y&lt;/code&gt; — &lt;strong&gt;Yank&lt;/strong&gt;: Copies text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt;&lt;/code&gt; — &lt;strong&gt;Indent&lt;/strong&gt;: Shifts text to the right (crucial for YAML).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v&lt;/code&gt; — &lt;strong&gt;Visual&lt;/strong&gt;: Starts selection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. The Nouns (Where to do it)
&lt;/h4&gt;

&lt;p&gt;In Vim, these are called &lt;strong&gt;Motions&lt;/strong&gt; or &lt;strong&gt;Text Objects&lt;/strong&gt;. They define the target of your action.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;w&lt;/code&gt; — Word: From the cursor to the end of the word.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s&lt;/code&gt; — Sentence: A full sentence.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;p&lt;/code&gt; — Paragraph: A block of code/text separated by empty lines.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;i&lt;/code&gt; — Inside: A modifier that ignores the surrounding characters (brackets, quotes).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a&lt;/code&gt; — Around: A modifier that includes the surrounding characters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;t&lt;/code&gt; — &lt;strong&gt;Tag&lt;/strong&gt;: For HTML/XML tags.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Building "Sentences" (The Magic)
&lt;/h4&gt;

&lt;p&gt;When you combine them, you realize you don't need "shortcuts" anymore. You just speak the language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;di"&lt;/code&gt;&lt;/strong&gt; → &lt;em&gt;Delete Inside Quotes&lt;/em&gt;: Perfect for clearing an environment variable value like &lt;code&gt;DB_PASSWORD="secret"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cap&lt;/code&gt;&lt;/strong&gt; → &lt;em&gt;Change Around Paragraph&lt;/em&gt;: Deletes an entire block of code and lets you rewrite it from scratch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;y3w&lt;/code&gt;&lt;/strong&gt; → &lt;em&gt;Yank 3 Words&lt;/em&gt;: Copies the next three words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ci{&lt;/code&gt;&lt;/strong&gt; → &lt;em&gt;Change Inside Brackets&lt;/em&gt;: Instantly clears a dictionary or a code block in Python/JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;2yy&lt;/code&gt;&lt;/strong&gt; → &lt;em&gt;2 Lines&lt;/em&gt;: Copy two lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why this is a Game Changer for DevOps
&lt;/h4&gt;

&lt;p&gt;Imagine you are editing a &lt;code&gt;docker-compose.yml&lt;/code&gt;. You need to change the image name.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-Vim way: Move mouse, click, hold backspace, type new image.&lt;/li&gt;
&lt;li&gt;Vim way: Place cursor anywhere on the image line and type &lt;strong&gt;&lt;code&gt;ci"&lt;/code&gt;&lt;/strong&gt;. Done. You are already typing the new name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "Verb + Noun" logic reduces cognitive overhead by turning edits into predictable patterns. Instead of memorizing obscure shortcuts, you are applying a consistent syntax to the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Minimal production-safe Vim configuration (&lt;code&gt;.vimrc&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;When you are on a production server, you rarely have the luxury of installing heavy plugins or custom themes. You need a configuration that is &lt;strong&gt;lightweight, portable, and powerful.&lt;/strong&gt; Create a &lt;code&gt;.vimrc&lt;/code&gt; file in your home directory and add these lines. The logic behind this setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;" --- Minimal Vim setup ---
syntax on                   "&lt;/span&gt; Enable syntax highlighting
&lt;span class="nb"&gt;set &lt;/span&gt;number                  &lt;span class="s2"&gt;" Show absolute line number
set relativenumber          "&lt;/span&gt; Show relative line numbers
&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;tabstop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4               &lt;span class="s2"&gt;" Number of visual spaces per TAB
set softtabstop=4           "&lt;/span&gt; Number of spaces &lt;span class="k"&gt;in &lt;/span&gt;tab when editing
&lt;span class="nb"&gt;set &lt;/span&gt;expandtab               &lt;span class="s2"&gt;" Tabs are spaces (crucial for Python/YAML)
set ignorecase              "&lt;/span&gt; Case insensitive searching
&lt;span class="nb"&gt;set &lt;/span&gt;smartcase               &lt;span class="s2"&gt;" Case sensitive if search contains uppercase
set undofile                "&lt;/span&gt; Maintain undo &lt;span class="nb"&gt;history &lt;/span&gt;between sessions
&lt;span class="nb"&gt;set &lt;/span&gt;cursorline              &lt;span class="s2"&gt;" Highlight the current line
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why these specific lines?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;set relativenumber&lt;/code&gt; (The Jump Master): This one completely changes how you move around. With &lt;code&gt;number&lt;/code&gt;, you just see your current line. But with &lt;code&gt;relativenumber&lt;/code&gt;, every line gets a number showing how far it is from where you are. &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Picture this: you spot an error 12 lines above. No more guesswork or holding the up arrow forever. You see “12” next to that line, type &lt;code&gt;12k&lt;/code&gt;, and boom—you’re there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;set undofile&lt;/code&gt; (The Time Machine): Normally, Vim wipes out your undo history as soon as you close a file.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Real life: You tweak a config, save, restart the service, and suddenly nothing works. Five minutes later, you realize you messed up. With &lt;code&gt;undofile&lt;/code&gt;, you just reopen the file and start hitting &lt;code&gt;u&lt;/code&gt;—it’ll undo changes from your last session. Absolute lifesaver when you’re scrambling to fix things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;set expandtab&lt;/code&gt; &amp;amp; &lt;code&gt;set tabstop=4&lt;/code&gt; (The YAML Guard): Anyone who’s fought with Docker, Kubernetes, or Ansible knows the pain—a single Tab character where spaces should be, and your whole setup falls apart. These settings make Vim insert four spaces every time you hit Tab, so your YAML and Python files stay squeaky clean.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;set ignorecase&lt;/code&gt; &amp;amp; &lt;code&gt;set smartcase&lt;/code&gt; (Efficient Hunting): Digging through a giant log? You don’t want to think about “Error” vs “error.”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Here’s how it works: &lt;code&gt;ignorecase&lt;/code&gt; makes all searches lowercase by default, but if you type a capital letter, &lt;code&gt;smartcase&lt;/code&gt; kicks in and matches exactly. You find stuff faster, no fuss.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;set cursorline&lt;/code&gt; (The Visual Anchor): Ever lose track of your cursor in a wall of white-on-black text? This draws a subtle highlight under your current line, so your eyes don’t get lost when you’re picking through big config files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Plugin Trap: Why "Vanilla" is Your Best Friend
&lt;/h4&gt;

&lt;p&gt;Scroll through Vim tutorials on YouTube and you’ll spot all kinds of flashy setups—glowing status bars, file trees like NERDTree, AI-powered autocompletion. Looks cool on your laptop. But honestly, for DevOps and operations engineers, that stuff can set you up for trouble.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Reality of Remote Work:&lt;/strong&gt; In your daily job, you will often find yourself inside a restricted environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A locked-down production server where you cannot download external plugins from GitHub.&lt;/li&gt;
&lt;li&gt;A temporary Docker container that will be destroyed in 10 minutes.&lt;/li&gt;
&lt;li&gt;A machine with no internet access (Air-gapped environment).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Minimal is Better:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero Setup Time: If your productivity depends on 20 plugins, you are useless the moment you SSH into a clean server. An experienced engineer is just as fast on a "bare-bones" Vim as they are on their local machine.&lt;/li&gt;
&lt;li&gt;Compatibility: Plugins can break between different Vim versions (or if you encounter the older &lt;code&gt;vi&lt;/code&gt;). Native commands work exactly the same way they did 30 years ago.&lt;/li&gt;
&lt;li&gt;The professional proficiency: There is a specific kind of respect you earn when your colleagues see you perform complex refactoring on a "naked" terminal without any fancy visual aids.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Strategy:&lt;/strong&gt; Learn the &lt;strong&gt;native&lt;/strong&gt; ways to do what plugins do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't need a file tree? Use &lt;code&gt;:Ex&lt;/code&gt; (the built-in Netrw file explorer).&lt;/li&gt;
&lt;li&gt;Don't need a fancy status bar? Use &lt;code&gt;Ctrl + g&lt;/code&gt; to see your location.&lt;/li&gt;
&lt;li&gt;Don't need a search plugin? Use &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;:grep&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Master the tool, not the plugins. You want to be the engineer who brings the skills to the server, not the one who needs to bring their whole "toolbox" just to fix a single line of code.&lt;/p&gt;

&lt;p&gt;There is nothing more soul-crushing than SSH-ing into a jump host to fix a SEV-1 issue, only to find that your fancy Neovim config is throwing Lua errors because the server is running an ancient version of GLIBC. In restricted production environments, tooling is minimal. Travel light or get stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Efficient Navigation: Don't Walk, Jump
&lt;/h2&gt;

&lt;p&gt;The arrow keys are the enemy of speed. In a standard editor, to move to a specific character at the end of a line, you hold the right arrow or use the mouse. In Vim, you &lt;strong&gt;jump&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  In-line movement: The "Find" and "Till" commands
&lt;/h3&gt;

&lt;p&gt;As an operations engineer, you often edit long lines—think of an &lt;code&gt;ENTRYPOINT&lt;/code&gt; in a &lt;code&gt;Dockerfile&lt;/code&gt; or a long list of arguments in a &lt;code&gt;bash&lt;/code&gt; script.&lt;/p&gt;

&lt;p&gt;Vim gives you two surgical tools for horizontal movement: &lt;code&gt;f&lt;/code&gt; and &lt;code&gt;t&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;f&lt;/code&gt; (Find):&lt;/strong&gt; Moves the cursor &lt;strong&gt;forward&lt;/strong&gt; to the next occurrence of a character.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Example:&lt;/em&gt; You are at the start of a line: &lt;code&gt;IMAGE="python:3.11-slim"&lt;/code&gt;. You want to change the version. Type &lt;strong&gt;&lt;code&gt;f:&lt;/code&gt;&lt;/strong&gt; and your cursor jumps directly to the colon.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;t&lt;/code&gt; (Till): Moves the cursor &lt;strong&gt;forward&lt;/strong&gt; to the character &lt;em&gt;just before&lt;/em&gt; the one you specify.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Example:&lt;/em&gt; On that same line, type &lt;strong&gt;&lt;code&gt;t"&lt;/code&gt;&lt;/strong&gt; to jump right to the end of the version number, but before the closing quote.&lt;/li&gt;
&lt;li&gt;The "Backwards" version: Use capital &lt;strong&gt;&lt;code&gt;F&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;T&lt;/code&gt;&lt;/strong&gt; to perform the same jumps in the opposite direction (to the left).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Advanced tip: You can combine these with your verbs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;df_&lt;/code&gt;&lt;/strong&gt;: Delete everything from the cursor forward until (and including) the underscore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ct.&lt;/code&gt;&lt;/strong&gt;: Change everything up to the dot (perfect for fixing IP addresses or domain names).
#### Search as Navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most people use search (&lt;code&gt;/&lt;/code&gt;) to find a word. A pro uses it to move.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Instead of hitting &lt;code&gt;j&lt;/code&gt; (down) twenty times to get to the &lt;code&gt;volumes:&lt;/code&gt; section in a &lt;code&gt;docker-compose.yml&lt;/code&gt;, just type &lt;strong&gt;&lt;code&gt;/vol&lt;/code&gt;&lt;/strong&gt; and hit &lt;code&gt;Enter&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;n&lt;/code&gt;&lt;/strong&gt;: Jump to the next match.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;N&lt;/code&gt;&lt;/strong&gt;: Jump to the previous match.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining &lt;code&gt;f&lt;/code&gt;, &lt;code&gt;}&lt;/code&gt;, and &lt;code&gt;/&lt;/code&gt;, you stop "walking" through your files and start "zipping" across them. Your eyes stay on the code, and your hands stay on the home row.**&lt;/p&gt;

&lt;h4&gt;
  
  
  File Movement: Mastering the Structure with &lt;code&gt;%&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;In the world of on-call support, you aren't just looking at plain text; you are looking at structured data. Whether it's a massive &lt;code&gt;JSON&lt;/code&gt; response from an API, a complex &lt;code&gt;Kubernetes&lt;/code&gt; manifest, or a nested &lt;code&gt;Nginx&lt;/code&gt; configuration, losing track of where a block starts and ends is a common headache.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The % (The Bracket Matcher):&lt;/strong&gt; This is your "breadcrumb" tool. Place your cursor on any brace &lt;code&gt;{&lt;/code&gt;, bracket &lt;code&gt;[&lt;/code&gt;, or parenthesis &lt;code&gt;(&lt;/code&gt; and hit &lt;code&gt;%&lt;/code&gt;. Vim will instantly teleport your cursor to the matching closing character. Hit &lt;code&gt;%&lt;/code&gt; again to jump back to the start.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Operations use-case:&lt;/strong&gt; You are debugging a &lt;code&gt;docker-compose.yml&lt;/code&gt; (converted to JSON) or a massive &lt;code&gt;HCL&lt;/code&gt; file in Terraform. Instead of scrolling and squinting at indentation, you hit &lt;code&gt;%&lt;/code&gt; and you’re at the bottom.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Combining with Verbs (The Power Move):&lt;/strong&gt; Remember the &lt;strong&gt;Verb + Noun&lt;/strong&gt; logic? You can use &lt;code&gt;%&lt;/code&gt; as a noun.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;d%&lt;/code&gt;&lt;/strong&gt;: Delete everything from the current bracket to its match. This is the fastest way to wipe out an entire misconfigured JSON object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;v%&lt;/code&gt;&lt;/strong&gt;: Visually select the entire block. Useful if you need to copy a specific section of a config.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Moving by Line Numbers:&lt;/strong&gt; If a colleague tells you, "Check the error on line 452," don't scroll.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Type &lt;strong&gt;&lt;code&gt;452G&lt;/code&gt;&lt;/strong&gt; (or &lt;strong&gt;&lt;code&gt;:452&lt;/code&gt;&lt;/strong&gt;) to jump directly to that line.&lt;/p&gt;

&lt;p&gt;Type &lt;strong&gt;&lt;code&gt;gg&lt;/code&gt;&lt;/strong&gt; to go to the very first line.&lt;/p&gt;

&lt;p&gt;Type &lt;strong&gt;&lt;code&gt;G&lt;/code&gt;&lt;/strong&gt; to go to the very last line (perfect for checking the latest entries in a log file).&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Search as Navigation: Teleporting to the Target
&lt;/h4&gt;

&lt;p&gt;When you are dealing with a 2000-line &lt;code&gt;syslog&lt;/code&gt; or a sprawling &lt;code&gt;Terraform&lt;/code&gt; state file, scrolling is a waste of time. A professional operations engineer uses the search function as a &lt;strong&gt;teleportation device&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Forward Search (&lt;code&gt;/&lt;/code&gt;):&lt;/strong&gt; Hit &lt;code&gt;/&lt;/code&gt;, type your search term (e.g., &lt;code&gt;/database&lt;/code&gt;), and hit &lt;code&gt;Enter&lt;/code&gt;. You are now at the first match.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;n&lt;/code&gt;&lt;/strong&gt;: Jump to the next occurrence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;N&lt;/code&gt;&lt;/strong&gt;: Jump to the previous occurrence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Backward Search (&lt;code&gt;?&lt;/code&gt;):&lt;/strong&gt; This is often overlooked but critical for log analysis. If you just opened a log file and jumped to the bottom (&lt;code&gt;G&lt;/code&gt;), you want to search &lt;strong&gt;upwards&lt;/strong&gt; for the latest errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Type &lt;code&gt;?ERROR&lt;/code&gt; to find the most recent incident.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The "Star" Trick (&lt;code&gt;*&lt;/code&gt;):&lt;/strong&gt; This is perhaps the most powerful "hidden" navigation tool. Place your cursor on any word (for example, a variable name like &lt;code&gt;POSTGRES_USER&lt;/code&gt;) and hit &lt;code&gt;*&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Vim will instantly jump to the &lt;strong&gt;next&lt;/strong&gt; time that exact word appears in the file.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;#&lt;/code&gt; to jump to the &lt;strong&gt;previous&lt;/strong&gt; one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operations use-case:&lt;/strong&gt; You see a weird variable in a script. Hit &lt;code&gt;*&lt;/code&gt; repeatedly to trace every place that variable is used or modified without typing a single character.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The Pro Strategy: Searching to Edit&lt;/strong&gt; Combined with the &lt;strong&gt;Verb + Noun&lt;/strong&gt; logic, you can perform actions across the file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;d/warning&lt;/code&gt;&lt;/strong&gt;: Delete everything from your current position until the first occurrence of the word "warning".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By mastering &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, and &lt;code&gt;*&lt;/code&gt;, you stop looking for things and start arriving at them.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Heavy Lifting: Search, Replace, and Macros
&lt;/h2&gt;

&lt;p&gt;In most editors, "Find and Replace All" feels like playing with fire. One wrong move and you’ve made a mess. In Vim, it’s a different story — you get the kind of control that lets you zero in on exactly what you want to change, no more, no less.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Search &amp;amp; Replace: Command-Line Precision
&lt;/h3&gt;

&lt;p&gt;Here’s the classic Vim way to search and replace: &lt;code&gt;:%s/search_term/replace_term/gc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let’s walk through what each part does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:&lt;/code&gt; drops you into command-line mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;%&lt;/code&gt; tells Vim to run the command across the whole file, not just the line you’re on.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s&lt;/code&gt; stands for substitute.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/search/replace/&lt;/code&gt; is your before-and-after.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g&lt;/code&gt; means change every match on each line, not just the first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c&lt;/code&gt; asks for confirmation each time it finds a match.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why the &lt;code&gt;c&lt;/code&gt; Flag Matters
&lt;/h3&gt;

&lt;p&gt;When you’re editing a production config, “Replace All” is just asking for trouble. Maybe your search term pops up somewhere unexpected, like inside another word. The &lt;code&gt;c&lt;/code&gt; flag makes Vim pause at each match and ask you what to do: &lt;code&gt;replace with new_term? (y/n/a/q/l)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;y&lt;/code&gt;: Yes, go ahead and change this one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n&lt;/code&gt;: Nope, leave it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a&lt;/code&gt;: All — just do the rest, stop asking.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;q&lt;/code&gt;: Quit — stop right here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-World Example: You have to change every port &lt;code&gt;80&lt;/code&gt; to &lt;code&gt;8080&lt;/code&gt;, but you don’t want to touch a stray comment or the wrong IP address. The &lt;code&gt;c&lt;/code&gt; flag lets you check each change as you go. In a few seconds, you can avoid a huge mistake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Tip: Ditch the Leaning Toothpicks
&lt;/h3&gt;

&lt;p&gt;If you’re always editing file paths, you know the pain of escaping slashes: &lt;code&gt;:%s/\/var\/lib\/docker/\/data\/docker/g&lt;/code&gt;. Total eyesore. Vim lets you pick another delimiter, so you can write: &lt;code&gt;:%s#/var/lib/docker#/data/docker#gc&lt;/code&gt; — way cleaner, way less typing, and you won’t lose track of slashes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Macros: Work Smarter, Not Harder
&lt;/h3&gt;

&lt;p&gt;Let’s say you get a dump of 50 IP addresses. You need to turn them into a YAML list for Ansible or Kubernetes. Sure, you could go line by line, adding a dash and a comment over and over. That’s tedious and slow. Or you can just record a macro in Vim, run it once, and let Vim do the grunt work for you.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The Macro Logic: &lt;code&gt;q&lt;/code&gt; + [register] + [actions] + &lt;code&gt;q&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;q&lt;/code&gt;&lt;/strong&gt;: Start recording.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;a&lt;/code&gt;&lt;/strong&gt;: Choose a "register" (a letter like 'a' where the macro will be stored).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Perform your edits&lt;/strong&gt;: (Vim records every keystroke).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;q&lt;/code&gt;&lt;/strong&gt;: Stop recording.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;@a&lt;/code&gt;&lt;/strong&gt;: Play back the macro once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;50@a&lt;/code&gt;&lt;/strong&gt;: Play back the macro 50 times.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Real-World Case: Formatting an IP List for YAML&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;You have a file that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.10
10.0.0.5
172.16.0.22
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need it to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.1.10"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.5"&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step-by-step Macro:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Put your cursor at the start of the first line.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;qa&lt;/code&gt;&lt;/strong&gt; (Start recording into register 'a').&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;I&lt;/code&gt;&lt;/strong&gt; (Capital I to insert at the very beginning of the line).&lt;/li&gt;
&lt;li&gt;Type &lt;strong&gt;&lt;code&gt;- ip: "&lt;/code&gt;&lt;/strong&gt; then hit &lt;strong&gt;&lt;code&gt;Esc&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;A&lt;/code&gt;&lt;/strong&gt; (Capital A to append at the end of the line).&lt;/li&gt;
&lt;li&gt;Type &lt;strong&gt;&lt;code&gt;"&lt;/code&gt;&lt;/strong&gt; then hit &lt;strong&gt;&lt;code&gt;Esc&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;j0&lt;/code&gt;&lt;/strong&gt; (Move down one line and go to the beginning). &lt;strong&gt;This is crucial&lt;/strong&gt; so the next playback starts in the right spot.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;q&lt;/code&gt;&lt;/strong&gt; (Stop recording).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, just type &lt;strong&gt;&lt;code&gt;50@a&lt;/code&gt;&lt;/strong&gt;. Watch as Vim transforms the next 50 lines in a split second.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why this is a "Superpower"
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Consistency: Unlike manual typing, a macro never makes a typo. If it works on the first line, it works on the next thousand.&lt;/li&gt;
&lt;li&gt;Complex Refactoring: You can use macros to delete specific columns in a CSV, wrap text in tags, or even run terminal commands on specific lines.&lt;/li&gt;
&lt;li&gt;Brain Power: It shifts your job from "data entry clerk" to "automation architect."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Multi-tasking inside the Terminal
&lt;/h2&gt;

&lt;p&gt;Don’t close Vim just to check another file. Seriously, that’s extra work you don’t need. One of the best things about Vim is how easily it lets you juggle multiple files without ever leaving the editor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Splits: The Side-by-Side View
&lt;/h3&gt;

&lt;p&gt;Splits change everything. If you haven’t played with them yet, give it a shot. They’re a lifesaver when you need to compare config files, glance at some reference code, or just keep two things open so you don’t lose your place.&lt;/p&gt;

&lt;p&gt;Want to see files side by side? Type &lt;code&gt;:vsplit filename&lt;/code&gt; and boom—they’re both there. If you’re working on a wide monitor, this is gold. Maybe you keep your &lt;code&gt;Dockerfile&lt;/code&gt; on one side and your &lt;code&gt;docker-compose.yml&lt;/code&gt; on the other. Suddenly, matching up ports and volumes gets a whole lot easier.&lt;/p&gt;

&lt;p&gt;If you’d rather stack files on top of each other, go with &lt;code&gt;:split filename&lt;/code&gt; for a horizontal split. That’s perfect if you want a small window up top for reference and your main file below.&lt;/p&gt;

&lt;p&gt;Moving between these windows is a breeze. Just hit &lt;code&gt;Ctrl+w&lt;/code&gt; plus a direction key (&lt;code&gt;h&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;k&lt;/code&gt;, &lt;code&gt;l&lt;/code&gt;). Or use the arrow keys if that feels more comfortable. To hop to the next window, hit &lt;code&gt;Ctrl+w, w&lt;/code&gt;. Need to swap their spots? &lt;code&gt;Ctrl+w, r&lt;/code&gt; flips them instantly. It’s all pretty straightforward.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tabs: Organized Workspaces
&lt;/h4&gt;

&lt;p&gt;While splits are for looking at files simultaneously, &lt;strong&gt;Tabs&lt;/strong&gt; are for organizing different tasks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;:tabnew filename&lt;/code&gt;&lt;/strong&gt;: Opens a file in a completely new "page".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gt&lt;/code&gt;&lt;/strong&gt;: Go to the &lt;strong&gt;next&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gT&lt;/code&gt;&lt;/strong&gt;: Go to the &lt;strong&gt;previous&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Practical strategy:&lt;/em&gt; Use Tab 1 for your main configuration and Tab 2 for browsing logs in the same Vim session.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Power of Diffing
&lt;/h4&gt;

&lt;p&gt;If you need to find why a service works on Server A but fails on Server B, use &lt;strong&gt;Vimdiff&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the terminal: &lt;code&gt;vimdiff file1.conf file2.conf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Vim will open both files and highlight the exact differences in red. It’s the fastest way to spot a missing semicolon or an incorrect IP address between environments.
#### The Secret Command: Integrating the Shell&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vim isn't just a text editor; it’s a shell-aware power tool. The &lt;code&gt;:r !&lt;/code&gt; command (read from bang) allows you to execute any Linux command and dump its output directly into your current buffer at the cursor's position.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Syntax:&lt;/strong&gt; &lt;code&gt;:r !&amp;lt;command&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Real-World Operations Use Cases
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documenting an Incident:&lt;/strong&gt; If you are writing a post-mortem or a log entry and need the exact timestamp:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Type: &lt;code&gt;:r !date&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; &lt;code&gt;Mon Jan 19 12:45:22 UTC 2026&lt;/code&gt; appears instantly on the line below.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Populating Configs with File Lists:&lt;/strong&gt; You are editing an Nginx config and need to include a list of all &lt;code&gt;.conf&lt;/code&gt; files in a directory:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Type: &lt;code&gt;:r !ls /etc/nginx/conf.d/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; All filenames are inserted directly into your config. No more manual typing or typos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grabbing Network Info:&lt;/strong&gt; Need to put the server's public IP into a script?&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Type: &lt;code&gt;:r !curl -s ifconfig.me&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Your external IP is fetched and pasted automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Advanced: Filtering Text through Shell
&lt;/h4&gt;

&lt;p&gt;You can also send text &lt;em&gt;from&lt;/em&gt; Vim &lt;em&gt;to&lt;/em&gt; a command and get it back.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; You have a messy list of unsorted IDs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; Highlight them in Visual Mode and type &lt;strong&gt;&lt;code&gt;:!sort&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Vim sends those lines to the Linux &lt;code&gt;sort&lt;/code&gt; utility and replaces them with the sorted output.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With tricks like these, Vim isn’t just a text editor—it’s pretty much an extension of your terminal. You’re not just editing files; you’re wielding the full power of your OS, right inside Vim.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Real-world Scenarios (The Practical Meat)
&lt;/h2&gt;

&lt;p&gt;In the quiet of a local dev environment, speed is a luxury. In the middle of an incident, speed is a metric. Here is how you apply "The Vim Way" to incident-response scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario A: Fixing Nginx Under High Pressure
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1q5kzt4kvhkw1cqy3r0u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1q5kzt4kvhkw1cqy3r0u.gif" alt="Quickly fixing a typo in the Nginx config file" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Situation: You just pushed a config change. Nginx fails to reload. You run &lt;code&gt;nginx -t&lt;/code&gt; and it screams: &lt;code&gt;syntax error in /etc/nginx/sites-enabled/starcom.org:25&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Amateur Way:&lt;/strong&gt; 1. Open the file: &lt;code&gt;/etc/nginx/sites-enabled/starcom.org&lt;/code&gt;. 2. Hold the down arrow to reach line 25. 3. Realize you scrolled too fast, move back up. 4. Squint at the screen to find the typo.&lt;/p&gt;

&lt;p&gt;Efficient approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Precision Entry:&lt;/strong&gt; Open the file exactly where the error is: &lt;code&gt;vim +/proxy_pass /etc/nginx/sites-enabled/starcom.org&lt;/code&gt; &lt;em&gt;(Vim opens, and your cursor is already blinking on the broken line).&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Anchor:&lt;/strong&gt; Thanks to &lt;code&gt;set cursorline&lt;/code&gt; in your &lt;code&gt;.vimrc&lt;/code&gt;, the problematic line is highlighted. You don't need to look for it; it’s looking at you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Sniper Fix:&lt;/strong&gt; You see &lt;code&gt;proxy_passs&lt;/code&gt;. You need to kill that last 's'. You don't enter Insert mode. You just type: &lt;strong&gt;&lt;code&gt;$x&lt;/code&gt;&lt;/strong&gt; &lt;em&gt;(&lt;code&gt;$&lt;/code&gt; jumps to the end of the line, &lt;code&gt;x&lt;/code&gt; deletes the character under the cursor).&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Ghost Exit:&lt;/strong&gt; Hit &lt;code&gt;Esc&lt;/code&gt; then &lt;strong&gt;&lt;code&gt;ZZ&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total time: 2 seconds. You’ve fixed the bug, saved the file, and triggered the reload before the "Amateur" even found the right line.&lt;/p&gt;

&lt;p&gt;P.S. The domain and config files in this example are for demonstration purposes only and do not belong to me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario B: Working with large log files
&lt;/h3&gt;

&lt;p&gt;The Situation: A critical service crashed, and the only evidence is buried somewhere in a 500MB &lt;code&gt;.log&lt;/code&gt; file. You try to open it with a standard editor, and the UI freezes. You try &lt;code&gt;cat&lt;/code&gt;, and your terminal is flooded with millions of lines you don't need.&lt;/p&gt;

&lt;p&gt;Why Vim Wins: Unlike modern "heavy" editors, Vim doesn't try to load the entire 500MB file into the GUI memory. It uses a swap file and only loads the portion of the file you are currently viewing into RAM. It is built to be "memory-efficient" by design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Efficient approach:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Search to Start&lt;/strong&gt;: Don't wait for the file to "load." Open the file and immediately jump to the end where the most recent errors are: &lt;code&gt;G&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The "Needle in the Haystack" Hunt&lt;/strong&gt;: You know the crash happened around 02:15 AM. Use backward search: &lt;code&gt;?02:15&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Surgical Extract&lt;/strong&gt;: You found the 50 lines of the stack trace. You need to send them to a developer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mark the start of the block with &lt;strong&gt;&lt;code&gt;ma&lt;/code&gt;&lt;/strong&gt; (sets a local "mark" named 'a').&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Move to the end of the trace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type &lt;strong&gt;&lt;code&gt;:'a,.w crash_dump.txt&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Vim just took the text from mark 'a' to the current line (&lt;code&gt;.&lt;/code&gt;) and wrote it (&lt;code&gt;w&lt;/code&gt;) into a brand new small file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Total time:&lt;/strong&gt; Under 30 seconds. You’ve extracted the critical data without ever needing to download the 500MB monster to your local machine or causing an Out-Of-Memory (OOM) error on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario C: Mass Commenting in &lt;code&gt;docker-compose.yaml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4g3q8064t3w0pnakzyb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4g3q8064t3w0pnakzyb.gif" alt="Mass commenting multiple lines in docker-compose.yaml using a keyboard shortcut" width="650" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Situation:&lt;/strong&gt; You are troubleshooting a stack. You need to temporarily disable three sidecar containers and their volumes in a &lt;code&gt;docker-compose.yml&lt;/code&gt; file—about 25 lines of YAML.&lt;/p&gt;

&lt;p&gt;Manual approach: 1. Enter Insert mode. 2. Type &lt;code&gt;#&lt;/code&gt;. 3. Arrow key down. 4. Type &lt;code&gt;#&lt;/code&gt;. 5. Repeat 25 times. (And probably mess up the indentation).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Engineer’s Approach (Visual Block Mode):&lt;/strong&gt; Vim allows you to edit &lt;strong&gt;vertically&lt;/strong&gt;. This is the secret to mass-editing structured files.&lt;/p&gt;

&lt;p&gt;Instead of wasting time in Insert mode, use &lt;strong&gt;Visual Block&lt;/strong&gt; to edit vertically. It’s a three-second operation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Select&lt;/strong&gt;: Drop your cursor at the start of the block and hit &lt;strong&gt;&lt;code&gt;Ctrl+v&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expand&lt;/strong&gt;: Tap &lt;strong&gt;&lt;code&gt;j&lt;/code&gt;&lt;/strong&gt; to highlight the vertical sliver across all lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt;: Hit &lt;strong&gt;&lt;code&gt;Shift+i&lt;/code&gt;&lt;/strong&gt;, type &lt;strong&gt;&lt;code&gt;#&lt;/code&gt;&lt;/strong&gt;, and then &lt;strong&gt;&lt;code&gt;Esc&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vim will automatically replicate that &lt;code&gt;#&lt;/code&gt; across the entire selection once you hit Escape. To &lt;strong&gt;uncomment&lt;/strong&gt;, just repeat the selection with &lt;strong&gt;&lt;code&gt;Ctrl+v&lt;/code&gt;&lt;/strong&gt; and hit &lt;strong&gt;&lt;code&gt;d&lt;/code&gt;&lt;/strong&gt; to delete the column of &lt;code&gt;#&lt;/code&gt; characters instantly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The Result:&lt;/em&gt; Vim instantly replicates that &lt;code&gt;#&lt;/code&gt; on every single line you selected.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Pro-Tip: Vertical Surgery &amp;amp; The "Silent Exit"
&lt;/h3&gt;

&lt;p&gt;In high-pressure ops, every second counts toward your MTTR. Speed comes from staying in Normal Mode and avoiding the command line (:) whenever possible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mass Uncommenting: To quickly strip comments from a block, use &lt;code&gt;Ctrl+v&lt;/code&gt; to select the vertical column of &lt;code&gt;#&lt;/code&gt; characters and hit d. They all vanish at once. To mass-comment, use &lt;code&gt;Ctrl+v&lt;/code&gt;, select the lines, hit &lt;code&gt;Shift+i&lt;/code&gt;, type &lt;code&gt;#&lt;/code&gt;, and hit &lt;code&gt;Esc&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Silent Exit (ZZ &amp;amp; ZQ): Stop wasting keystrokes on : and searching for &lt;code&gt;q&lt;/code&gt; or &lt;code&gt;w&lt;/code&gt;. You can vanish directly from Normal Mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ZZ&lt;/code&gt;: Save changes and exit. It’s the equivalent of &lt;code&gt;:wq&lt;/code&gt;, but faster because it doesn't require command-line mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ZQ&lt;/code&gt;: Quit without saving. The equivalent of &lt;code&gt;:q!&lt;/code&gt;, essential when you've opened a production config just to "look" and want to ensure zero accidental edits.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Conclusion: The Path to Muscle Memory
&lt;/h2&gt;

&lt;p&gt;Learning Vim feels a lot like picking up a guitar for the first time. It’s clumsy, kind of frustrating, and honestly, you might wonder why you’re even bothering. Your fingers go wild, nothing feels natural, and quitting starts to sound pretty good. But if you push through, things shift. Suddenly, the keys start meaning something. Your hands just know where to go. One day you realize you’re not thinking about how to type anymore—you’re just getting things done.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Survive Past Day Two
&lt;/h3&gt;

&lt;p&gt;People always talk about this magical “Vim Peak,” but The reality is—most folks never get there. They drop out in what I call the “Vim Valley,” stuck during those rough early days where everything’s confusing and slow. Want to make it across? Here’s what actually helps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The One Window Rule: Don’t toss your favorite IDE yet. Just use Vim for quick terminal edits—editing a crontab, tweaking .bashrc, that kind of thing. Stack up those small wins. They count.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unbind Your Arrow Keys: Want to get better, faster? Put this in your .vimrc:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="nb"&gt;noremap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Up&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Nop&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;noremap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Down&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Nop&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;noremap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Left&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Nop&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;noremap&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Right&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;Nop&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s rough, but it forces you to use h, j, k, and l. That’s where real muscle memory starts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The vimtutor Ritual: Every Linux box has vimtutor. Run it once a day for a week. It takes fifteen minutes, tops. After a week, you’ll be faster than most of your friends.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Operational ROI
&lt;/h3&gt;

&lt;p&gt;If you're on-call, or in operations, or working in DevOps, you’re not just learning another editor. You’re buying peace of mind.&lt;/p&gt;

&lt;p&gt;When production’s on fire, you don’t have time to fumble through key commands. You want to focus on what matters - hunting down that nasty bug hiding in your config. With Vim, the editor melts away. You just think, “delete that part” and it happens.&lt;/p&gt;

&lt;p&gt;The terminal is your home base. Vim is your power tool. Learn it, and you’ll never panic when you’re dropped onto a blank server at 3 AM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Challenge
&lt;/h3&gt;

&lt;p&gt;Want to stop Googling “how do I quit Vim” for good? Here’s how you level up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make vimtutor your morning warmup. Fire it up every day for five days. That’s it—fifteen minutes each time. By the end, your fingers will start reaching for dw or y before your brain even catches up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extreme Mode: Go cold turkey—ditch VS Code for a full week. It’s rough in the beginning, no question. But without the crutch of a GUI, you adapt fast. After a few days, you won’t even miss your mouse when moving a line.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mastering the syntax moves the complexity into muscle memory. During &lt;strong&gt;high-priority outages&lt;/strong&gt;, you can't afford the cognitive load of remembering commands—you need to focus entirely on the recovery.&lt;/p&gt;

&lt;p&gt;Vim provides a consistent editing environment across servers, containers, and recovery systems. A small set of core motions and operators is enough to perform safe and fast edits during incident response and production maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;If you work in Site Reliability or Support, you know the number that matters—MTTR. Mean Time To Recovery. Every minute counts. Every outage costs money, hurts your team, and wrecks your night. Getting fast in the terminal isn’t about showing off. It’s about saving those precious minutes and getting things running again.&lt;/p&gt;

&lt;p&gt;That’s the difference. An amateur gets lost in logs, clicking around for five minutes. An experienced Vim user can locate the error, apply a precise edit, and restart the service quickly.&lt;/p&gt;

&lt;p&gt;Master Vim and you’re not just adding another tool. You’re giving yourself an edge for the moments when everything’s on the line. Vim is a practical tool for fast, low-risk edits in production and incident workflows. Learn a small set of core motions and operators first, then expand gradually.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>vim</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Stop Shipping Fat Python Docker Images: Multi-Stage Builds Explained</title>
      <dc:creator>Alexey Cherednichenko</dc:creator>
      <pubDate>Sat, 17 Jan 2026 19:35:49 +0000</pubDate>
      <link>https://dev.to/alaxay8/stop-shipping-fat-python-docker-images-multi-stage-builds-explained-2ag1</link>
      <guid>https://dev.to/alaxay8/stop-shipping-fat-python-docker-images-multi-stage-builds-explained-2ag1</guid>
      <description>&lt;p&gt;Hey folks! Let’s be honest — if you’ve ever dockerized a basic Python app, you’ve probably run into this: you build a tiny Flask “Hello World” project, and somehow the Docker image balloons to almost a gigabyte. Feels a bit like shipping a single pizza in a cargo freighter.&lt;/p&gt;

&lt;p&gt;So, what’s happening? Why do these Python images blow up in size?&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The "Fat" Image Problem
&lt;/h3&gt;

&lt;p&gt;1) &lt;strong&gt;Invisible Bloat:&lt;/strong&gt; &lt;code&gt;pip install&lt;/code&gt; pulls in all sorts of stuff behind the scenes. If your app uses &lt;code&gt;pandas&lt;/code&gt; or &lt;code&gt;psycopg2&lt;/code&gt;, for example, you’ll need build tools like &lt;code&gt;gcc&lt;/code&gt; just to get them installed. Once pip’s done, those tools stick around, eating up space for no good reason.&lt;br&gt;
2) &lt;strong&gt;The Base Image Trap:&lt;/strong&gt; If you start with something like &lt;code&gt;FROM python:3.11&lt;/code&gt;, you’re basically dragging along a full-blown Debian OS — utilities, tools, and a bunch of extras that your app will never touch in production.&lt;br&gt;
3) &lt;strong&gt;Hidden Leftovers:&lt;/strong&gt; Pip caches, temporary files, and build artifacts. They just pile up, taking up room and serving no purpose once the image is built.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why It Matters (The L3 Perspective)
&lt;/h3&gt;

&lt;p&gt;I deal with this stuff every day as an &lt;strong&gt;L3 Support Engineer&lt;/strong&gt;. And it’s not just about storage. Every unnecessary megabyte is another opportunity for attackers. If someone breaks into your container, all those extra tools (like &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, or &lt;code&gt;git&lt;/code&gt;) give them a head start to compile exploits or move laterally through your network.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Plan
&lt;/h3&gt;

&lt;p&gt;We want images that are lean, locked down, and fast. Here’s what we are going to achieve:&lt;/p&gt;

&lt;p&gt;1) &lt;strong&gt;Shrink your image size&lt;/strong&gt; by as much as 80%.&lt;br&gt;
2) &lt;strong&gt;Strip out build tools&lt;/strong&gt; before production for tighter security.&lt;br&gt;
3) &lt;strong&gt;Speed up your CI/CD pipeline.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The trick? &lt;strong&gt;Multi-Stage Builds.&lt;/strong&gt; Let’s get into it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 0: The Project Setup
&lt;/h2&gt;

&lt;p&gt;Before we even touch Docker, let’s get our bearings with a simple Python app. The setup looks like what you’d see in most real-world projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project Structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-python-app/ 
├── templates/ 
│   └── index.html
├── app.py 
├── requirements.txt 
└── Dockerfile

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;templates/index.html&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Docker Slim App&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Docker Multi-stage is working!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This image is slim, secure, and production-ready.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;app.py&lt;/strong&gt; (A simple Flask app):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;requirements.txt&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask==3.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Use a Specific Base Image
&lt;/h2&gt;

&lt;p&gt;Let’s talk about the &lt;code&gt;FROM&lt;/code&gt; instruction. This line sets the foundation for your whole Dockerfile. Most folks just slap in &lt;code&gt;FROM python:3.11&lt;/code&gt; and call it a day. The problem? That image is a monster.&lt;/p&gt;

&lt;p&gt;You actually have two lighter choices: &lt;strong&gt;Alpine&lt;/strong&gt; and &lt;strong&gt;Slim&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, Alpine gets a lot of hype because it’s tiny—the base is only about 5MB. Sounds great, right? But if you’re working with Python, Alpine often causes additional complexity for Python workloads because most wheels expect glibc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here’s the deal:&lt;/strong&gt; Alpine uses &lt;code&gt;musl libc&lt;/code&gt;, but almost all pre-built Python packages (wheels) expect &lt;code&gt;glibc&lt;/code&gt; (which you’ll find on Debian or Ubuntu). With Alpine, &lt;code&gt;pip&lt;/code&gt; can’t use those wheels. Instead, it tries to build everything from scratch. That quick 30-second install? Suddenly, it drags out for 20 minutes and usually dies with some confusing error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pro Move:&lt;/strong&gt; Go with &lt;strong&gt;Python-Slim&lt;/strong&gt;. It’s basically Debian, but trimmed down. You get &lt;code&gt;glibc&lt;/code&gt; compatibility, so your packages install fast, and the base image is still only about 120MB. No headaches, no drama.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Start with the slim version for stability and speed
FROM python:3.11-slim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The "Builder" Stage (Compiling Dependencies)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: The Builder&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="c"&gt;# Install system dependencies needed for building packages&lt;/span&gt;
&lt;span class="c"&gt;# Since we are on a Debian-based image (like Ubuntu), we use apt&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    gcc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Create the virtual environment&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv /opt/venv
&lt;span class="c"&gt;# Ensure subsequent commands use the venv&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH"&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why bother with a virtual environment inside Docker?
&lt;/h3&gt;

&lt;p&gt;Yeah, Docker containers are already isolated. But when we use &lt;code&gt;/opt/venv&lt;/code&gt;, we keep all our installed libraries tucked away in one spot. Later, instead of chasing files all over the place, we just grab that one folder and we’re good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The "Final" Stage (The Lean Runner)
&lt;/h2&gt;

&lt;p&gt;Now for the fun part. We start fresh with a slim image—no compilers, no build tools, nothing extra.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 2: The Production Image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy the virtual environment from the builder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/venv /opt/venv&lt;/span&gt;

&lt;span class="c"&gt;# Bring in the app code and templates&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Set some environment variables&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH" \&lt;/span&gt;
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  So, what’s actually going on here?
&lt;/h3&gt;

&lt;p&gt;1) COPY --from=builder: This is what makes multi-stage builds awesome. We tell Docker, “Hey, grab only the &lt;code&gt;/opt/venv&lt;/code&gt; folder from our heavy builder image and drop it in here.”&lt;/p&gt;

&lt;p&gt;2) PYTHONDONTWRITEBYTECODE=1: Stops Python from scattering &lt;code&gt;.pyc&lt;/code&gt; files everywhere. Keeps things tidy.&lt;/p&gt;

&lt;p&gt;3) PYTHONUNBUFFERED=1: Super important for Docker. It makes sure all your logs and print statements show up right away in the terminal, not stuck waiting somewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Security Hardening (Non-Root User)
&lt;/h2&gt;

&lt;p&gt;Never run your app as &lt;code&gt;root&lt;/code&gt; in production. If someone finds a bug in your code and breaks in, you don’t want them running wild as the most powerful user in the system.&lt;/p&gt;

&lt;p&gt;To lock things down, you need a dedicated user with limited access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updated Final Stage:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 2: The Production Image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;

&lt;span class="c"&gt;# Add a new group and user just for your app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;-r&lt;/span&gt; appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; appuser appuser

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Bring in the virtual environment from the builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/venv /opt/venv&lt;/span&gt;

&lt;span class="c"&gt;# Copy your code and hand it over to the new user&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appuser . .&lt;/span&gt;

&lt;span class="c"&gt;# Set up environment variables&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH" \&lt;/span&gt;
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

&lt;span class="c"&gt;# Make sure everything below runs as the non-root user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why bother with all this?
&lt;/h3&gt;

&lt;p&gt;1) &lt;code&gt;groupadd&lt;/code&gt; &amp;amp; &lt;code&gt;useradd&lt;/code&gt;: You’re making a user called &lt;code&gt;appuser&lt;/code&gt;. No password, no home directory, nothing fancy. It’s just there to run your app and nothing else.&lt;br&gt;
2) &lt;code&gt;--chown=appuser:appuser&lt;/code&gt;: As soon as your code lands in the image, it belongs to &lt;code&gt;appuser&lt;/code&gt;. No root access, no extra powers.&lt;br&gt;
3) &lt;code&gt;USER appuser&lt;/code&gt;: This one’s huge. Now, everything after this — including your app itself — only runs with the limited permissions of &lt;code&gt;appuser&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Don't Forget the .dockerignore
&lt;/h2&gt;

&lt;p&gt;Think of &lt;code&gt;.dockerignore&lt;/code&gt; as Docker’s version of &lt;code&gt;.gitignore&lt;/code&gt;. It tells Docker, “Hey, when I copy everything over, skip these files.”&lt;/p&gt;

&lt;p&gt;If you miss this, you could end up copying your whole local &lt;code&gt;venv&lt;/code&gt; into the image and mess up the clean one you built earlier.&lt;/p&gt;

&lt;p&gt;Just drop a &lt;code&gt;.dockerignore&lt;/code&gt; file in your project’s root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.git
.gitignore
__pycache__/
*.pyc
venv/
.env
.vscode/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why does this matter so much?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Stops needless rebuilds: If you change a random local file (like a README or git log), Docker won’t waste time rebuilding your image layers from scratch. &lt;/li&gt;
&lt;li&gt;Protects sensitive stuff: You don’t want &lt;code&gt;.git&lt;/code&gt; history or &lt;code&gt;.env&lt;/code&gt; files — especially with secrets or passwords — ending up in your production image. &lt;/li&gt;
&lt;li&gt;Speeds things up: Your build context shrinks. Less junk to copy means your build kicks off almost immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Let’s see what we started with and where we ended up. These changes took a clunky, insecure container and turned it into something you’d actually want running in production.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard FROM python:3.11&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Our Multi-Stage Image&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Final Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~467 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~154 MB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs as &lt;code&gt;root&lt;/code&gt; (Dangerous)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Non-root user&lt;/strong&gt; (Secure)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compilers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;build-essential&lt;/code&gt; present&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Removed&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cleanliness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Contains pip cache &amp;amp; junk&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Only necessary files&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Shrinking the image by almost 85% isn’t just good for bragging rights. Now deployments go faster, you save on storage, and your security gets a serious upgrade. Take it from me—when your image is lean, you have fewer weird issues, and way fewer middle-of-the-night emergencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before you send your next Python image to the registry, double-check these:
&lt;/h2&gt;

&lt;p&gt;Before you push your next Python image to the registry, run through this quick checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a -slim base image. Don’t pull in the whole kitchen sink unless you have to. &lt;/li&gt;
&lt;li&gt;Build in stages. Compilers and headers stay in the builder; keep your final image tidy. &lt;/li&gt;
&lt;li&gt;Bundle dependencies with &lt;code&gt;venv&lt;/code&gt;. Keep everything in a single, portable folder. &lt;/li&gt;
&lt;li&gt;Never run as root. Spin up a dedicated &lt;code&gt;appuser&lt;/code&gt; instead. &lt;/li&gt;
&lt;li&gt;Check your &lt;code&gt;.dockerignore&lt;/code&gt;. Don’t ship .git, secrets, or &lt;code&gt;__pycache__&lt;/code&gt; by accident. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Set PYTHONUNBUFFERED=1. That way, your logs actually show up in real time.
&lt;/h2&gt;

&lt;p&gt;Honestly, good DevOps isn’t just about getting things to work. It’s about doing it efficiently and securely. Multi-stage builds are an easy win if you want to step up your Docker game.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean Up!
&lt;/h3&gt;

&lt;p&gt;One last thing—multi-stage builds sometimes leave behind a &lt;code&gt;&amp;lt;none&amp;gt;&lt;/code&gt; image (that’s just the builder stage hanging around). Tidy up your system with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker image prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Happy hacking!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>docker</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>SSH Hardening on Linux: A Practical Step-by-Step Guide</title>
      <dc:creator>Alexey Cherednichenko</dc:creator>
      <pubDate>Fri, 16 Jan 2026 19:23:02 +0000</pubDate>
      <link>https://dev.to/alaxay8/ssh-hardening-on-linux-a-practical-step-by-step-guide-4agb</link>
      <guid>https://dev.to/alaxay8/ssh-hardening-on-linux-a-practical-step-by-step-guide-4agb</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Hey folks! Let’s talk about SSH—the go-to tool for managing remote Linux servers. It’s super useful, but honestly, out-of-the-box, it’s like a magnet for automated attacks. Here’s what I’ve learned as an L3 Support Engineer about locking down SSH: &lt;br&gt;
1) &lt;strong&gt;Ditch passwords.&lt;/strong&gt; Go with Key-Based Authentication. It’s way stronger. &lt;br&gt;
2) &lt;strong&gt;Don’t make it easy for attackers.&lt;/strong&gt; Change the default port to reduce automated bot noise (not a real security boundary) and turn off root login. &lt;br&gt;
3) &lt;strong&gt;Set up Fail2Ban.&lt;/strong&gt; It automatically blocks brute-force attempts, so you don’t have to keep an eye on logs all day. &lt;br&gt;
4) &lt;strong&gt;Double-check your setup.&lt;/strong&gt; Audit your config so you don’t accidentally lock yourself out. That’s it—these steps make SSH a lot tougher for attackers to mess with.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Ditch Passwords for SSH Keys
&lt;/h2&gt;

&lt;p&gt;Passwords just don’t cut it when it comes to keeping your server safe. Automated bots hammer away, guessing thousands of passwords every minute. But if you switch to SSH keys, brute-force attacks basically stop being a problem. &lt;/p&gt;

&lt;p&gt;Making a Secure Key Pair &lt;/p&gt;

&lt;p&gt;Open up your local machine and make yourself a strong, modern key with the Ed25519 algorithm. It’s not only faster than old-school RSA, it’s a lot more secure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -t ed25519 -C "admin@example.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Change the default port and turn off root login
&lt;/h2&gt;

&lt;p&gt;Once you’ve set up your keys, it’s time to lock the door for real and ditch passwords altogether. We're going to tweak the &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; file so only key-based logins work. &lt;/p&gt;

&lt;p&gt;Heads up—leave one SSH session running while you’re editing. If you mess something up and restart the service, you don’t want to lock yourself out. &lt;/p&gt;

&lt;p&gt;Go ahead and open up the config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo vi /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Essential Security Tweaks
&lt;/h3&gt;

&lt;p&gt;Update the following directives in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. Change the default port to dodge automated bots
Port 2222 

# 2. Disable root login. Always use a sudo user instead.
PermitRootLogin no

# 3. Disable password authentication entirely
PasswordAuthentication no

# 4. Limit the number of failed attempts before disconnection
MaxAuthTries 3

# 5. Only allow specific users to log in (Whitelist)
AllowUsers your_username
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving the file, check the configuration for syntax errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo sshd -t
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there is no output, restart the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Set up Fail2Ban.
&lt;/h2&gt;

&lt;p&gt;Even after you move SSH to a different port and turn off password access, bots still sniff around. Your logs will fill up with failed attempts. That’s where &lt;strong&gt;Fail2Ban&lt;/strong&gt; comes in. It watches for trouble and blocks shady IPs automatically, so you don’t have to babysit your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;First, install the service on your server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install fail2ban -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;Instead of editing the main config, we create a &lt;code&gt;.local&lt;/code&gt; file to override the defaults. This ensures your settings stay intact during updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo vi /etc/fail2ban/jail.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following configuration for your hardened SSH service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[sshd]
enabled = true
port    = 2222
filter  = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 10m
bantime = 1h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1) &lt;strong&gt;maxretry&lt;/strong&gt;: Number of failures before a ban.&lt;br&gt;
2) &lt;strong&gt;bantime&lt;/strong&gt;: How long the offender is blocked (1 hour in this case).&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Restart the service to turn on protection
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Want to see which IPs Fail2Ban has blocked or check your jail’s status? Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo fail2ban-client status sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Locking down your servers isn’t something you set and forget. Ditching password logins, switching up default ports, and letting Fail2Ban handle brute force attacks—these moves shut the door on most lazy hackers. &lt;/p&gt;

&lt;p&gt;Honestly, this is just the baseline for any serious server. If you’re ready to bump things up, try adding Two-Factor Authentication or set up a VPN or jump host for extra protection. &lt;/p&gt;

&lt;p&gt;Got a favorite SSH security tip? Or maybe you’ve run into a bizarre lockout? Drop your stories in the comments—I’d love to hear them.&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>security</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
