LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

vim/neovim for DevOps: Your Terminal Editor, Supercharged

vimneovimcliproductivitydevopseditor

Nearly every server you’ll ever SSH into has vi or vim. No GUI, no VS Code tunnel, no escape hatch — just you and the terminal. That’s the survival argument for learning vim. But there’s a stronger one: once it clicks, vim is genuinely the fastest way to edit text. This guide gets you from “how do I exit?” to “I can’t imagine working without this.”

vim vs neovim

vim is the classic, ships everywhere, rock-solid. neovim is a modern fork with a better plugin API (Lua instead of Vimscript), built-in LSP support, and a more active plugin ecosystem. The core editing model is identical — everything in this guide applies to both.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Install vim
apt install vim          # Debian/Ubuntu
dnf install vim          # Fedora/RHEL
brew install vim         # macOS

# Install neovim
apt install neovim
dnf install neovim
brew install neovim

# Check version
vim --version
nvim --version

For daily driving, use neovim. For remote servers, be comfortable with whatever vim is installed.


The Mental Model: Modes

The single concept that unlocks vim is modal editing. Vim has separate modes for navigation and for inserting text. Most editors conflate these. vim does not.

Mode How to Enter Purpose
Normal Esc Navigation, operations, commands
Insert i, a, o, I, A, O Typing text
Visual v, V, Ctrl+v Selecting text
Command : Ex commands (save, quit, search/replace)

You start in Normal mode. You return to Normal mode with Esc. Everything else flows from there.


Surviving: The Essential Commands

i       Insert before cursor
a       Insert after cursor
o       Open new line below and insert
O       Open new line above and insert
Esc     Return to Normal mode

:w      Write (save)
:q      Quit
:wq     Write and quit
:q!     Quit without saving
ZZ      Write and quit (shorthand)
ZQ      Quit without saving (shorthand)

Moving Around

In Normal mode, the keyboard becomes a navigation pad. Learn these and never touch the arrow keys again.

Basic Motion

h j k l     Left / Down / Up / Right

w           Forward one word (start)
b           Backward one word (start)
e           Forward to end of word
W B E       Same but skip punctuation (WORD)

0           Start of line
^           First non-blank character
$           End of line

gg          Top of file
G           Bottom of file
5G          Go to line 5
Ctrl+d      Half-page down
Ctrl+u      Half-page up
Ctrl+f      Full-page down
Ctrl+b      Full-page up

Search Navigation

/pattern    Search forward
?pattern    Search backward
n           Next match
N           Previous match
*           Search for word under cursor (forward)
#           Search for word under cursor (backward)

Jump to Character

f{char}     Jump forward to char on current line
F{char}     Jump backward to char
t{char}     Jump forward to just before char
T{char}     Jump backward to just after char
;           Repeat last f/F/t/T
,           Repeat in opposite direction

Marks and Jumps

ma          Set mark 'a' at cursor
'a          Jump to line of mark 'a'
`a          Jump to exact position of mark 'a'
Ctrl+o      Jump back (older position)
Ctrl+i      Jump forward (newer position)
''          Jump to last jump position

Operators: The Grammar of vim

vim commands follow a grammar: operator + motion (or operator + text object).

Operators

d       Delete
c       Change (delete and enter Insert mode)
y       Yank (copy)
>       Indent right
<       Indent left
=       Auto-indent
g~      Toggle case
gu      Lowercase
gU      Uppercase

Text Objects

iw / aw     Inner word / A word (including space)
is / as     Inner sentence / A sentence
ip / ap     Inner paragraph / A paragraph
i" / a"     Inner quotes / A quotes (including quotes)
i' / a'     Same for single quotes
i( / a(     Inner parens / A parens
i{ / a{     Inner braces / A braces
i[ / a[     Inner brackets / A brackets
it / at     Inner HTML tag / A tag

Examples

dw          Delete word forward
db          Delete word backward
diw         Delete inner word (cursor anywhere in word)
daw         Delete a word (including trailing space)
di"         Delete contents of double-quoted string
da"         Delete quoted string including quotes
d$          Delete to end of line
d0          Delete to start of line
dd          Delete entire line
5dd         Delete 5 lines

ciw         Change inner word
ci"         Change inside quotes
ci(         Change inside parens
c$          Change to end of line
cc          Change entire line

yiw         Yank inner word
yy          Yank entire line
5yy         Yank 5 lines

>ip         Indent paragraph
=G          Auto-indent from here to end of file

Counts

Prefix any command with a number to repeat it:

5j          Move down 5 lines
3dw         Delete 3 words
10dd        Delete 10 lines
2>j         Indent 2 lines

Registers and Clipboard

vim has named registers for storing yanked/deleted text.

"ayy        Yank line into register 'a'
"ap         Paste from register 'a'
"byiw       Yank word into register 'b'

"+y         Yank to system clipboard (requires clipboard support)
"+p         Paste from system clipboard
"*y / "*p   X11 primary selection (Linux)

:reg        Show all register contents
""          Unnamed register (last yank/delete)
"0          Last explicit yank
"1-"9       Delete history (most recent = "1)
"-          Small delete register (less than one line)

Macros: Automating Repetitive Edits

Macros record a sequence of Normal mode commands and replay them.

qa          Start recording macro into register 'a'
...         Do your edits
q           Stop recording
@a          Replay macro 'a'
@@          Replay last macro
5@a         Replay macro 'a' 5 times

Real-World Example

You have a file with lines like:

server1
server2
server3

You want:

"server1",
"server2",
"server3",

Position cursor on server1, then:

qa          Start recording
I"          Insert quote at start
Esc
A",         Append quote-comma at end
Esc
j           Move to next line
q           Stop recording
2@a         Apply to next 2 lines

Search and Replace

:s/old/new/         Replace first match on current line
:s/old/new/g        Replace all matches on current line
:%s/old/new/g       Replace all matches in file
:%s/old/new/gc      Replace all, confirm each
:10,20s/old/new/g   Replace in lines 10-20

# Use \1 for capture groups
:%s/\(foo\)/[\1]/g  Wrap "foo" in brackets

# Case insensitive
:%s/old/new/gi

# Whole word only
:%s/\bold\b/new/g

Visual selection + :s operates on selected lines automatically:

# Select lines with V, then:
:'<,'>s/old/new/g

Working with Multiple Files

Buffers

:e filename         Open file
:ls                 List open buffers
:b2                 Switch to buffer 2
:bn / :bp           Next / previous buffer
:bd                 Close buffer
:bufdo %s/old/new/g Operate on all open buffers

Splits

:sp filename        Horizontal split
:vsp filename       Vertical split
Ctrl+w h/j/k/l      Navigate splits
Ctrl+w H/J/K/L      Move split
Ctrl+w =            Equalize split sizes
Ctrl+w +/-          Resize horizontal
Ctrl+w >/<          Resize vertical

Tabs

:tabnew filename    Open file in new tab
gt / gT             Next / previous tab
:tabclose           Close tab

The netrw File Browser

vim ships with netrw, a built-in file browser. No plugin required.

1
vim .               Open netrw in current directory

Inside netrw:

Enter       Open file/directory
-           Go up one directory
%           Create new file
d           Create new directory
D           Delete file/directory
R           Rename
i           Cycle view (thin/long/wide/tree)
gh          Toggle hidden files

Configuration: ~/.vimrc / ~/.config/nvim/init.vim

A minimal but useful starting config:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
" === Core settings ===
set nocompatible            " Don't try to be vi-compatible
set encoding=utf-8

" === Display ===
set number                  " Line numbers
set relativenumber          " Relative line numbers (great for d5j etc.)
set cursorline              " Highlight current line
set scrolloff=8             " Keep 8 lines visible above/below cursor
set colorcolumn=80,120      " Visual column guides
set signcolumn=yes          " Always show sign column (for LSP, git)
syntax on

" === Indentation ===
set expandtab               " Spaces instead of tabs
set tabstop=4
set shiftwidth=4
set softtabstop=4
set autoindent
set smartindent

" === Search ===
set hlsearch                " Highlight search results
set incsearch               " Incremental search
set ignorecase              " Case-insensitive search...
set smartcase               " ...unless capital letters used

" === Usability ===
set hidden                  " Allow unsaved buffers in background
set wildmenu                " Better command-line completion
set wildmode=list:longest
set backspace=indent,eol,start
set clipboard=unnamedplus   " Use system clipboard
set mouse=a                 " Mouse support
set updatetime=250          " Faster updates (affects CursorHold)
set timeoutlen=500

" === Persistent undo ===
set undodir=~/.vim/undo
set undofile

" === Key mappings ===
let mapleader = " "         " Space as leader key

" Clear search highlight
nnoremap <leader>/ :nohlsearch<CR>

" Better window navigation
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l

" Move lines up/down
nnoremap <A-j> :m .+1<CR>==
nnoremap <A-k> :m .-2<CR>==
vnoremap <A-j> :m '>+1<CR>gv=gv
vnoremap <A-k> :m '<-2<CR>gv=gv

" Stay in indent mode
vnoremap < <gv
vnoremap > >gv

" Quick save
nnoremap <leader>w :w<CR>

" Open netrw
nnoremap <leader>e :Ex<CR>

Create the undo directory:

1
mkdir -p ~/.vim/undo

neovim with Lua: ~/.config/nvim/init.lua

neovim’s native config language is Lua, which is faster and more maintainable than Vimscript:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-- ~/.config/nvim/init.lua

-- Leader key
vim.g.mapleader = " "
vim.g.maplocalleader = " "

-- Options
local opt = vim.opt
opt.number = true
opt.relativenumber = true
opt.expandtab = true
opt.tabstop = 4
opt.shiftwidth = 4
opt.softtabstop = 4
opt.scrolloff = 8
opt.ignorecase = true
opt.smartcase = true
opt.hlsearch = true
opt.incsearch = true
opt.hidden = true
opt.clipboard = "unnamedplus"
opt.mouse = "a"
opt.updatetime = 250
opt.signcolumn = "yes"
opt.undofile = true
opt.cursorline = true

-- Keymaps
local map = vim.keymap.set

map("n", "<leader>/", ":nohlsearch<CR>")
map("n", "<C-h>", "<C-w>h")
map("n", "<C-j>", "<C-w>j")
map("n", "<C-k>", "<C-w>k")
map("n", "<C-l>", "<C-w>l")
map("n", "<leader>w", ":w<CR>")
map("n", "<leader>e", ":Ex<CR>")

map("v", "<", "<gv")
map("v", ">", ">gv")

map("n", "<A-j>", ":m .+1<CR>==")
map("n", "<A-k>", ":m .-2<CR>==")
map("v", "<A-j>", ":m '>+1<CR>gv=gv")
map("v", "<A-k>", ":m '<-2<CR>gv=gv")

Plugin Management

vim-plug (vim and neovim)

1
2
3
4
5
6
7
# Install vim-plug for vim
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

# Install vim-plug for neovim
curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

In ~/.vimrc or ~/.config/nvim/init.vim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
call plug#begin()

Plug 'tpope/vim-fugitive'           " Git integration
Plug 'tpope/vim-surround'           " Surround text objects
Plug 'tpope/vim-commentary'         " Toggle comments
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'             " Fuzzy finder
Plug 'airblade/vim-gitgutter'       " Git diff in gutter
Plug 'dense-analysis/ale'           " Linting

call plug#end()

Install: :PlugInstall

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
-- ~/.config/nvim/lua/plugins.lua
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git", "clone", "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  -- Fuzzy finder
  {
    "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
  },
  -- Syntax highlighting
  {
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
  },
  -- LSP
  {
    "neovim/nvim-lspconfig",
    dependencies = {
      "williamboman/mason.nvim",
      "williamboman/mason-lspconfig.nvim",
    },
  },
  -- Autocompletion
  {
    "hrsh7th/nvim-cmp",
    dependencies = {
      "hrsh7th/cmp-nvim-lsp",
      "hrsh7th/cmp-buffer",
      "hrsh7th/cmp-path",
      "L3MON4D3/LuaSnip",
    },
  },
  -- Git
  "tpope/vim-fugitive",
  "lewis6991/gitsigns.nvim",
  -- Surround
  "tpope/vim-surround",
  -- Comments
  "tpope/vim-commentary",
  -- Status line
  "nvim-lualine/lualine.nvim",
  -- File tree
  "nvim-tree/nvim-tree.lua",
})

Essential Plugins Explained

vim-surround

Add, change, or delete surrounding characters:

cs"'        Change surrounding " to '
cs({        Change surrounding ( to {
ds"         Delete surrounding "
ysiw"       Surround word with "
yss)        Surround entire line with ()

In Visual mode: S" surrounds selection with ".

vim-commentary

gcc         Toggle comment on current line
gc{motion}  Toggle comment on motion
gcap        Comment out paragraph
gc          (Visual mode) Toggle comment on selection

vim-fugitive

Full Git integration inside vim:

1
2
3
4
5
6
7
8
:Git status         " or :G
:Git add %          " Stage current file
:Git commit
:Git push
:Git log --oneline
:Gdiffsplit         " Diff current file against HEAD
:Gblame             " Annotate with git blame
:Gread              " Revert to HEAD version

fzf.vim / Telescope

Fast fuzzy finding:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
" fzf.vim
:Files              " Find files
:Buffers            " Switch buffers
:Rg pattern         " Ripgrep search
:BLines             " Search current buffer
:History            " Recent files

" Telescope (neovim)
:Telescope find_files
:Telescope live_grep
:Telescope buffers
:Telescope help_tags

Map these to leader keys:

1
2
3
4
5
6
-- neovim + Telescope
local telescope = require("telescope.builtin")
map("n", "<leader>ff", telescope.find_files)
map("n", "<leader>fg", telescope.live_grep)
map("n", "<leader>fb", telescope.buffers)
map("n", "<leader>fh", telescope.help_tags)

LSP in neovim

Language Server Protocol gives you IDE features (autocomplete, go-to-definition, diagnostics) in neovim. The mason.nvim plugin manages server installation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- Install mason and mason-lspconfig first (via lazy.nvim above)
require("mason").setup()
require("mason-lspconfig").setup({
  ensure_installed = {
    "lua_ls",
    "bashls",
    "pyright",
    "gopls",
    "yamlls",
    "dockerls",
    "terraformls",
    "ansiblels",
  },
})

-- Configure LSP keymaps on attach
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(event)
    local opts = { buffer = event.buf }
    map("n", "gd", vim.lsp.buf.definition, opts)
    map("n", "gr", vim.lsp.buf.references, opts)
    map("n", "K", vim.lsp.buf.hover, opts)
    map("n", "<leader>rn", vim.lsp.buf.rename, opts)
    map("n", "<leader>ca", vim.lsp.buf.code_action, opts)
    map("n", "[d", vim.diagnostic.goto_prev, opts)
    map("n", "]d", vim.diagnostic.goto_next, opts)
    map("n", "<leader>d", vim.diagnostic.open_float, opts)
  end,
})

Install servers interactively: :Mason


DevOps-Specific Workflows

Editing Config Files on Remote Servers

1
2
3
4
5
6
7
# Always use tmux first so you don't lose your session
ssh user@server
tmux new -s config

vim /etc/nginx/nginx.conf
# Make changes
# :w to save, :q to exit

Editing Multiple Config Files at Once

1
2
3
4
5
# Open all nginx config files
vim /etc/nginx/conf.d/*.conf

# Navigate with :bn :bp :ls
# Edit in all buffers: :bufdo %s/old_upstream/new_upstream/g | w

Diffing Files

1
2
3
4
5
6
# Side-by-side diff
vim -d file1 file2

# In vim: ]c and [c to jump between diff hunks
# do: diff obtain (pull change from other window)
# dp: diff put (push change to other window)

Running Shell Commands

1
2
3
4
5
:!command           " Run command and show output
:r !command         " Insert command output into buffer
:w !sudo tee %      " Save file as root (when you forgot sudo)
:%!jq .             " Pipe entire file through jq (pretty-print JSON)
:%!python3 -m json.tool

Editing Kubernetes YAML

With yamlls via mason:

1
2
3
4
5
6
" Open a manifest
vim deployment.yaml

" Jump between YAML keys with [ ] motions
" K shows hover docs for known fields
" gd jumps to definition of referenced resources

Inline kubectl

1
2
3
:r !kubectl get pods -o yaml
:w deployment.yaml
:!kubectl apply -f %

Useful Tips and Tricks

Repeat Last Change

. repeats the last change. This is one of the most powerful features in vim.

# Add semicolons to end of lines
A;<Esc>     Move cursor, then: .   .   .

Jump to Matching Bracket

% jumps between matching (), [], {}, and even if/endif in some filetypes.

Increment/Decrement Numbers

Ctrl+a      Increment number under cursor
Ctrl+x      Decrement number under cursor
5Ctrl+a     Increment by 5

Sort Lines

1
2
3
4
5
:sort               " Sort all lines
:'<,'>sort          " Sort selected lines
:sort!              " Sort in reverse
:sort u             " Sort and remove duplicates
:sort n             " Numerical sort

Column Editing (Visual Block)

Ctrl+v      Enter Visual Block mode
Select rows with j/k
I           Insert at start of each selected line
A           Append at end of each selected line
d           Delete selected block

Folding

zc          Close fold
zo          Open fold
za          Toggle fold
zR          Open all folds
zM          Close all folds
set foldmethod=indent   " Fold by indentation
set foldmethod=syntax   " Fold by syntax

Cheat Sheet

MOTION              OPERATOR            TEXT OBJECT
h j k l             d (delete)          iw (inner word)
w b e W B E         c (change)          aw (a word)
0 ^ $ gg G          y (yank)            i" a" i' a'
f{c} F{c}           > < (indent)        i( a( i{ a{
/{pat} n N          = (format)          ip ap (paragraph)
* #                 g~ gu gU (case)     it at (tag)
Ctrl+d/u/f/b

INSERT              COMMAND
i I a A o O         :w :q :wq :q!
Esc                 :e :ls :b :bd
                    :s :%s
                    :sp :vsp
                    :! (shell)
                    :r (read)

USEFUL
.       Repeat last change
u       Undo
Ctrl+r  Redo
%       Jump to matching bracket
*       Search word under cursor
"       Register prefix
@       Macro replay
q       Record macro

Learning Path

  1. Week 1vimtutor (run it in the terminal, takes ~30 min, do it twice)
  2. Week 2 — Use vim for all config file edits on servers. Resist the urge to reach for nano.
  3. Week 3 — Learn text objects (ciw, di", ca() and the . repeat command
  4. Week 4 — Set up a .vimrc with the settings above, add vim-surround and vim-commentary
  5. Month 2 — Switch to neovim, add Telescope and treesitter
  6. Month 3 — Add LSP via mason, configure for your primary language

The goal isn’t to memorize everything at once. It’s to incrementally replace mouse actions with keyboard commands until the muscle memory takes over. Each motion you internalize compounds with every other one.

1
2
# Best way to start right now
vimtutor

Comments