Neovim for DevOps Engineers: From SSH Terminals to Full Development
A comprehensive guide to why Neovim is the superior editor for DevOps work. Master remote development, infrastructure-as-code workflows, minimal dependencies, and why traditional IDEs fail in SSH environments. Learn the mental model of why Neovim beats VS Code and JetBrains when you're managing infrastructure.
Neovim for DevOps Engineers: From SSH Terminals to Full Development
Why Modal Editing and Minimal Dependencies Beat GUI-Heavy IDEs in Infrastructure Work
π― Introduction: The Problem DevOps Engineers Face
Let me start with a painful reality: Most DevOps engineers are using the wrong editor.
Not because theyβre making bad choices. But because theyβre forced to work in environments where traditional editors fail catastrophically.
The Scenario
Youβre managing infrastructure across multiple environments. You SSH into:
- A production server at 3 AM because something broke
- A Kubernetes control plane to debug a networking issue
- A developerβs machine to help troubleshoot their setup
- A CI/CD runner that has 500MB of disk space
- A bastion host with only vim installed
In these moments, VS Code? Doesnβt exist. JetBrains IDE? Canβt run. You need an editor that:
β
Runs on minimal resources
β
Works over SSH instantly
β
Requires no GUI infrastructure
β
Handles large infrastructure-as-code files efficiently
β
Integrates with Unix tools you already use
β
Doesn't require X11 forwarding or complex networking
β
Starts in milliseconds
β
Works perfectly over high-latency SSH connections
This is not a hypothetical problem. This is the daily reality of DevOps work.
The Traditional Problem
DevOps engineers reach for two options:
Option 1: Use vi/vim
- Works everywhere
- But learning curve is steep
- Configuration is obtuse
- No modern features (LSP, fuzzy search, etc.)
- Time investment for minimal gain
Option 2: Use VS Code with Remote SSH
- Modern experience
- Good when working on ONE machine
- But:
- Requires Node.js installed on remote
- Requires 200MB+ disk space for server
- Connection drops = lost editing session
- Complex setup for bastion hosts / jump servers
- Can't run on 500MB constrained environments
- Polling for file changes is expensive
- Security team blocks it (forwarded ports)
Neither option is optimal for infrastructure work.
What This Guide Is About
This is not βLearn Neovim in 100 easy steps.β
This is: Why Neovim is architecturally superior for DevOps work, and how to use it effectively in real infrastructure scenarios.
We will cover:
β
Why Neovim exists - The philosophy that made it better
β
Remote development model - How it actually works over SSH
β
Infrastructure-as-code workflows - Terraform, Ansible, scripts
β
Minimal dependencies - Why this matters
β
SSH scenarios - Bastion hosts, jump servers, production boxes
β
Configuration management - Making Neovim your own
β
DevOps-specific setup - LSP, linting, formatting for IaC
β
Productivity patterns - The workflows that actually matter
The perspective: How a senior DevOps engineer thinks about editor choices.
ποΈ Part 1: Why Neovim Exists (The Architectural Difference)
Before diving into how to use Neovim, understand why it was created and how that changes everything.
The Problem with Vim
Vim is incredible. It runs everywhere. Itβs fast. Itβs been refined for 30 years.
But Vim has fundamental architectural limitations:
Vim Architecture (simplified):
βββββββββββββββββββββββββββββββ
β Core Editor Engine β
β (written in C, 1991) β
βββββββββββββββββββββββββββββββ€
β VimL scripting language β
β (custom language, hard) β
βββββββββββββββββββββββββββββββ€
β Plugin system (VimL) β
β (limited, slow, isolated) β
βββββββββββββββββββββββββββββββ
Problems:
- Plugins are isolated processes (slow)
- VimL is a custom language (steep learning curve)
- Can't easily integrate modern tools (LSP, etc.)
- Configuration requires learning VimL
- Community fragmented between Vim and plugin authors
Neovim was created to fix these problems:
Neovim Architecture (Modern)
Neovim Architecture (redesigned):
βββββββββββββββββββββββββββββββββββββββ
β Core Editor Engine β
β (extracted from Vim, refactored) β
βββββββββββββββββββββββββββββββββββββββ€
β API Layer (msgpack RPC protocol) β
β (real network protocol) β
βββββββββββββββββββββββββββββββββββββββ€
β Plugin Ecosystem β
β - Lua scripting (fast, modern) β
β - External processes (real async) β
β - Language servers (LSP native) β
β - Terminal integration β
βββββββββββββββββββββββββββββββββββββββ
Advantages:
- Plugins talk to Neovim via RPC (real async)
- Lua is fast and modern (not VimL)
- Native LSP support (language servers)
- External processes aren't blocked
- Configuration is code, not commands
This architectural difference means:
Vim: "How do I make my editor do this?"
Answer: "Learn VimL and hope someone wrote a plugin"
Neovim: "How do I make my editor do this?"
Answer: "Write Lua code or use an existing plugin"
Why This Matters for DevOps
DevOps engineers work with:
- Multiple languages: Terraform, Python, Go, Bash, YAML, JSON
- LSP servers: pyright, gopls, terraform-ls, etc.
- Formatting tools: black, gofmt, prettier
- Linting: pylint, golangci-lint, terraform validate
- Integration tools: git, grep, find, make
Vim makes integrating these difficult. Neovim makes it native.
In Vim:
- Install language plugins
- Hope they work together
- Configuration is command-based
- Async requires vim-async-plugin
In Neovim:
- LSP integrates natively (language-agnostic)
- Async is built-in
- Configuration is Lua code (composable)
- Tools integrate cleanly
For DevOps work, where youβre constantly switching between tools and languages, Neovimβs architecture is superior.
π Part 2: Remote Development Model - How SSH Works with Neovim
The key to understanding Neovim for DevOps is understanding its remote development model.
The Three SSH Scenarios
Scenario 1: Direct SSH Connection
You SSH into a server and run Neovim directly:
ssh user@server.com
neovim /path/to/file
How it works:
Your Computer Server
βββββββββββββββ βββββββββββββββ
β SSH β β Neovim β
β Terminal ββββββββββββ€ Process β
β β β (remote) β
βββββββββββββββ βββββββββββββββ
- All editing happens on server
- Only terminal data transmitted (tiny)
- No files transferred
- Works over high-latency connections
- Works over low-bandwidth connections
Advantages:
β
No setup needed on server
β
Minimal disk space (Neovim = 10MB)
β
Minimal RAM (Neovim = 50MB)
β
Works over unreliable connections
β
No security concerns (no forwarding)
β
File changes instant (no polling)
β
Kernel tools available on server
Scenario 2: SSH with Neovim Remote (netrw)
You run Neovim locally but edit files on remote server via SSH:
nvim scp://user@server.com/path/to/file
How it works:
Your Computer (GUI/Terminal) Server
βββββββββββββββββββββββββββ ββββββββββββ
β Neovim Process β β SSH β
β - Editing locally ββββββββ€ Protocol β
β - Files synced via β ββββββββββββ
β SSH/SCP β
βββββββββββββββββββββββββββ
- Editing happens locally
- Files transferred on save
- Your configuration used
- Server just stores files
Advantages:
β
Use your local Neovim configuration
β
Local plugins available
β
Faster editing (no network latency)
β
Works with any remote file system
Disadvantages:
β Requires Neovim on local machine
β Requires SSH keys set up
β File syncing can lag
β Not good for collaborative work
Scenario 3: SSH with Local SSH Control
You SSH to a machine, but Neovim connects BACK to your local machine:
# On remote server:
ssh -R 22:localhost:22 user@local-machine
# Inside that SSH:
nvim scp://local-user@localhost/path/to/file
Advanced scenario (rarely needed).
The Key Insight: Terminal-Based Editing Scales
VS Code Remote SSH Model:
- VS Code runs locally
- Node.js server runs on remote
- WebSocket connection
- File polling/watching
- GUI elements transmitted
- Complex setup required
- Doesn't work in constrained environments
Neovim SSH Model:
- Neovim runs on remote (if needed)
- Or files accessed via SSH protocol
- Only terminal/keyboard data transmitted
- Minimal server resources
- Works in constrained environments
- Scale to hundreds of remote servers
This is why Neovim scales for DevOps. You can manage 50 servers, SSH to any one of them, edit infrastructure code instantly, with zero setup.
Real-World SSH Scenarios
Scenario A: Production Hotfix at 3 AM
Your machine (home, spotty WiFi)
β SSH
Production Bastion Host
β SSH
Production Server (down due to config error)
Traditional approach:
- VS Code can't connect through bastion (networking complexity)
- Have to tunnel, port forward, configure jump servers
- Setup takes 20 minutes
- Meanwhile service is down
Neovim approach:
ssh bastion-host
ssh production-server
nvim /etc/myapp/config.yaml
# Fix immediately
# Done in 1 minute
Scenario B: Terraform Deployment to Multiple Regions
You have:
- dev environment (AWS)
- staging environment (AWS)
- prod-us (AWS)
- prod-eu (AWS)
- prod-asia (AWS)
Traditional approach:
- Set up VS Code Remote on each environment
- Each setup takes 30 minutes
- 5 environments = 2.5 hours setup
- Switching between environments = context switch
Neovim approach:
# Alias in your ~/.ssh/config:
Host dev-terraform
HostName dev-bastion.internal
User terraform-deploy
# Then:
ssh dev-terraform
nvim terraform/main.tf
# Done, switch to another environment instantly
Scenario C: Debugging Ansible on Deployment Host
Your machine β SSH β Deployment host (has Ansible playbooks)
Playbook fails. Need to edit it.
Traditional:
- Option 1: SCP file locally, edit, SCP back (slow)
- Option 2: VS Code Remote (overkill, needs setup)
- Option 3: nano/vi (painful)
Neovim:
ssh deployment-host
nvim playbooks/deploy.yml
# Edit directly, test immediately
π§ Part 3: Infrastructure-as-Code Workflows
DevOps engineers primarily work with IaC. Letβs examine how Neovim excels here.
The Tools DevOps Uses
Terraform:
- *.tf files
- terraform-ls (language server)
- terraform fmt, terraform validate
Ansible:
- *.yml, *.yaml
- ansible-lint
- ansible-playbook (testing)
Kubernetes:
- *.yaml
- YAML files (no special tools)
- kubectl apply --dry-run (testing)
Python (automation scripts):
- *.py files
- pyright, pylint (linting)
- pytest (testing)
Bash/Shell (maintenance scripts):
- *.sh files
- shellcheck (linting)
- bash -n (validation)
Go (infrastructure tools):
- *.go files
- gopls (language server)
- go fmt, go vet
Traditional IDEs struggle here because:
β You're constantly switching languages
β No single IDE handles all of them well
β JetBrains has separate IDEs for Python, Go, etc.
β VS Code needs 20+ extensions
β Configuration becomes fragmented
Neovim handles this elegantly:
Neovim as Universal IaC Editor
Single configuration (init.lua):
1. Install LSP servers for each language
- terraform-ls
- pyright
- gopls
- yamllint (for YAML)
2. Configure formatters
- terraform fmt
- black (Python)
- gofmt (Go)
- prettier (JSON/YAML)
3. Configure linters
- terraform validate
- pylint
- golangci-lint
- ansible-lint
- shellcheck
4. Add file type detection
- *.tf β Terraform
- *.py β Python
- *.go β Go
- *.yaml β YAML
5. All languages use same keybindings
- gd β Go to definition
- gr β Find references
- K β Show documentation
- Space+f β Format
One editor. One configuration. All languages.
Real-World IaC Workflow Example
# terraform/main.tf - you're editing this
resource "aws_ec2_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro" # β cursor here
tags = {
Name = "web-server"
}
}
With Neovim + terraform-ls:
Press: K
Shows: AWS documentation for instance_type parameter
Shows: Valid values, examples, defaults
Press: gd
Jumps to: Where this ami is defined
Press: gr
Shows: All places using this ami ID
Press: Space+f
Runs: terraform fmt on the file
Reformats: Entire file according to Terraform standards
Press: ,t (custom keybinding)
Runs: terraform validate
Shows: Errors/warnings inline
All happening instantly, with no setup beyond initial Neovim config.
Infrastructure Testing in Neovim
# Edit your Ansible playbook
nvim deploy.yml
# Save the file (Neovim auto-formats with prettier/yamllint)
# Run the playbook in a split window
:terminal ansible-playbook deploy.yml --check
# Errors show up immediately
# Fix in one pane, test in another pane
# Switch panes (Neovim built-in splits)
Ctrl+w β (move to terminal pane)
This workflow is:
β
Fast (no file syncing)
β
Direct (see results instantly)
β
Integrated (all in one editor)
β
Flexible (your keyboard shortcuts)
π¦ Part 4: Minimal Dependencies (Why This Matters)
A core principle of DevOps is: minimize dependencies, maximize reliability.
This principle applies to your editor too.
Dependency Analysis
VS Code Remote SSH:
Requirements on remote server:
- Node.js β₯ 14
- 200MB+ disk space
- ssh-server
- curl or wget (for installation)
Disk footprint: 200-400MB
RAM usage: 300-500MB minimum
Startup time: 5-10 seconds
Complexity: Moderate (debugging remote connection issues is painful)
JetBrains IDEs:
Requirements on remote server:
- Java JVM β₯ 11
- 500MB+ disk space
- ssh-server
Disk footprint: 500MB-1GB
RAM usage: 800MB-1.5GB minimum
Startup time: 10-20 seconds
Complexity: High (JVM configuration, memory tuning)
Neovim:
Requirements on remote server:
- Linux/Unix kernel
- That's it
Disk footprint: 10-20MB (including plugins)
RAM usage: 50-100MB typical
Startup time: <100ms
Complexity: Low (pure Unix tooling)
Why Minimal Dependencies Matter
Scenario 1: Constrained Container Environment
Your CI/CD container has 500MB disk limit.
VS Code Remote: β Won't fit
JetBrains: β Won't fit
Neovim: β
Easily fits
Scenario 2: Running on Embedded System
You're managing OpenWrt router firmware configuration.
Hardware: 128MB RAM, 8MB flash storage
VS Code: β Impossible
JetBrains: β Impossible
Neovim: β
Runs fine
Scenario 3: Air-Gapped Environment
You have a machine that can't download packages from internet.
You need to provide pre-built editor.
VS Code Remote: β Hundreds of NPM dependencies, complex binary
JetBrains: β JVM complexity, many dependencies
Neovim: β
Single 15MB binary, zero runtime dependencies
Scenario 4: Serverless/Lambda Function Debugging
You have AWS Lambda@Edge function that needs editing.
Can't use: VS Code (too heavy), JetBrains (too heavy)
Can use: vim (painful), nano (very painful), Neovim (excellent)
Dependency Chain Matters
VS Code dependencies:
node.js β v8 β OpenSSL β glibc β kernel
That's 4 dependencies between you and the kernel.
Each one is a potential point of failure.
Each one needs security updates.
Neovim dependencies:
libc β kernel
That's 1 dependency.
Neovim itself is the trusted layer.
Single binary, simple to audit.
For infrastructure work, where reliability is paramount, minimal dependencies is a feature, not a limitation.
Self-Contained Deployments
DevOps principle: carry your tools with you.
Neovim approach:
1. Build Neovim from source in your CI system
2. Static binary (no runtime dependencies)
3. SCP to any server (no installation needed)
4. Works anywhere, instantly
VS Code approach:
1. Install Node.js on server
2. Download 200MB VSCode
3. Configure everything
4. Deal with platform differences
5. Troubleshoot when something breaks
π Part 5: Security and Reliability Implications
Using minimal-dependency tools has security consequences.
Attack Surface
VS Code Remote SSH:
- Node.js process (potential vulnerability)
- WebSocket server (potential vulnerability)
- File watching system (potential vulnerability)
- Authentication in VS Code (potential vulnerability)
Risk: More code = more potential exploits
Neovim:
- Single binary (well-audited, 30 years history)
- SSH protocol (network layer, not application layer)
- No background services
Risk: Minimal, focused
Supply Chain Security
VS Code gets updates weekly.
Each update is a risk.
"Security update" means: there was a vulnerability.
Neovim gets security updates occasionally.
Simpler codebase = fewer vulnerabilities.
Single responsibility = easier to audit.
Reliability in Production
Production emergency 3 AM:
You need to fix a configuration file IMMEDIATELY.
With VS Code Remote:
- Is Node.js running?
- Is the remote connection stable?
- Is disk full preventing server startup?
- Did WebSocket timeout?
- Need to restart server, reconnect, etc.
With Neovim:
- SSH connection exists? Edit directly.
- No setup needed.
- No processes to manage.
- Works in states where VS Code would fail.
βοΈ Part 6: DevOps-Specific Neovim Configuration
Now that you understand WHY Neovim, letβs set it up FOR DevOps WORK.
Essential Plugins for DevOps
-- ~/.config/nvim/init.lua
-- Plugin Manager (using packer.nvim)
local ensure_packer = function()
local fn = vim.fn
local install_path = fn.stdpath('data')..'/site/pack/packer/start/packer.nvim'
if fn.empty(fn.glob(install_path)) > 0 then
fn.system({'git', 'clone', '--depth', '1', 'https://github.com/wbthomason/packer.nvim', install_path})
return true
end
return false
end
local packer_bootstrap = ensure_packer()
require('packer').startup(function(use)
-- LSP Configuration
use 'neovim/nvim-lspconfig'
use 'williamboman/mason.nvim'
use 'williamboman/mason-lspconfig.nvim'
-- Completion
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-buffer'
use 'hrsh7th/cmp-path'
-- Snippets
use 'L3MON4D3/LuaSnip'
use 'saadparwaiz1/cmp_luasnip'
-- Telescope (fuzzy finder)
use 'nvim-telescope/telescope.nvim'
use 'nvim-lua/plenary.nvim'
-- Treesitter (syntax highlighting)
use 'nvim-treesitter/nvim-treesitter'
-- Git Integration
use 'tpope/vim-fugitive'
use 'lewis6991/gitsigns.nvim'
-- File Explorer
use 'kyazdani42/nvim-tree.lua'
-- Status Line
use 'nvim-lualine/lualine.nvim'
-- Theme
use 'folke/tokyonight.nvim'
if packer_bootstrap then
require('packer').sync()
end
end)
LSP Setup for Infrastructure Languages
-- ~/.config/nvim/after/plugin/lspconfig.lua
local lspconfig = require('lspconfig')
local mason_lspconfig = require('mason-lspconfig')
-- Automatically install language servers for these languages
mason_lspconfig.setup({
ensure_installed = {
'terraformls', -- Terraform
'pyright', -- Python
'gopls', -- Go
'yamlls', -- YAML
'bashls', -- Bash
'jsonls', -- JSON
},
automatic_installation = true,
})
-- Configure each language server
local lsp_config = {
on_attach = function(client, bufnr)
-- Enable completion
vim.bo.omnifunc = 'v:lua.vim.lsp.omnifunc'
-- Keybindings
local opts = { noremap = true, silent = true, buffer = bufnr }
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts) -- Go to definition
vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts) -- Find references
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts) -- Show documentation
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts) -- Rename
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts) -- Code actions
vim.keymap.set('n', '<leader>f', vim.lsp.buf.format, opts) -- Format
end,
capabilities = require('cmp_nvim_lsp').default_capabilities(),
}
-- Terraform
lspconfig.terraformls.setup(lsp_config)
-- Python
lspconfig.pyright.setup(lsp_config)
-- Go
lspconfig.gopls.setup(lsp_config)
-- YAML (for Ansible, Kubernetes)
lspconfig.yamlls.setup(lsp_config)
-- Bash
lspconfig.bashls.setup(lsp_config)
-- JSON
lspconfig.jsonls.setup(lsp_config)
-- Formatters
vim.api.nvim_command('autocmd BufWritePre *.tf silent! exec "!terraform fmt %"')
vim.api.nvim_command('autocmd BufWritePre *.py silent! exec "!black %"')
vim.api.nvim_command('autocmd BufWritePre *.go silent! exec "!gofmt -w %"')
vim.api.nvim_command('autocmd BufWritePre *.yml,*.yaml silent! exec "!prettier --write %"')
DevOps-Specific Keybindings
-- ~/.config/nvim/init.lua (keybindings section)
-- Leader key
vim.g.mapleader = ' '
-- File operations
vim.keymap.set('n', '<leader>w', ':w<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>q', ':q<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>x', ':x<CR>', { noremap = true, silent = true })
-- Terraform specific
vim.keymap.set('n', '<leader>tv', ':!terraform validate<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>tp', ':!terraform plan<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>ta', ':!terraform apply<CR>', { noremap = true, silent = true })
-- Ansible specific
vim.keymap.set('n', '<leader>al', ':!ansible-lint %<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>ac', ':!ansible-playbook % --check<CR>', { noremap = true, silent = true })
-- Terminal
vim.keymap.set('n', '<leader>t', ':terminal<CR>', { noremap = true, silent = true })
vim.keymap.set('t', '<Esc>', '<C-\\><C-n>', { noremap = true, silent = true })
-- Window navigation
vim.keymap.set('n', '<C-h>', '<C-w>h', { noremap = true, silent = true })
vim.keymap.set('n', '<C-j>', '<C-w>j', { noremap = true, silent = true })
vim.keymap.set('n', '<C-k>', '<C-w>k', { noremap = true, silent = true })
vim.keymap.set('n', '<C-l>', '<C-w>l', { noremap = true, silent = true })
-- Fuzzy finder
vim.keymap.set('n', '<leader>ff', ':Telescope find_files<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>fg', ':Telescope live_grep<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>fb', ':Telescope buffers<CR>', { noremap = true, silent = true })
-- Git
vim.keymap.set('n', '<leader>gs', ':Git status<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>gc', ':Git commit<CR>', { noremap = true, silent = true })
vim.keymap.set('n', '<leader>gp', ':Git push<CR>', { noremap = true, silent = true })
SSH Config for Easy Access
# ~/.ssh/config
# Production infrastructure
Host prod-bastion
HostName prod-bastion.company.com
User devops
IdentityFile ~/.ssh/id_rsa
ControlMaster auto
ControlPath ~/.ssh/control-%h-%p-%r
ControlPersist 600
Host prod-*
ProxyCommand ssh prod-bastion -W %h:%p
User devops
IdentityFile ~/.ssh/id_rsa
# Staging infrastructure
Host staging-bastion
HostName staging-bastion.company.com
User devops
IdentityFile ~/.ssh/id_rsa
Host staging-*
ProxyCommand ssh staging-bastion -W %h:%p
User devops
IdentityFile ~/.ssh/id_rsa
# Development
Host dev-*
HostName %h.company-dev.internal
User devops
IdentityFile ~/.ssh/id_rsa
Now you can:
# Jump to production and edit:
ssh prod-terraform-server
nvim terraform/main.tf
# Edit files locally from remote:
nvim scp://prod-terraform-server//home/devops/terraform/main.tf
# All configured, all keybindings ready
π Part 7: Real-World DevOps Scenarios
Letβs walk through actual workflows.
Scenario 1: Emergency Production Hotfix
Time: 3 AM
Issue: API returning 500 errors
Root cause: Configuration file corrupt
The workflow:
# 1. SSH to production (through bastion)
ssh prod-api-server
# 2. Open config file in Neovim
nvim /etc/myapp/config.yaml
# 3. Neovim opens (instantly, <100ms)
# 4. Find the issue (syntax error in YAML)
# 5. Fix it (modal editing is fast once you know it)
# 6. Save with auto-formatting
# 7. Validate configuration
# :terminal
# ./validate-config.sh /etc/myapp/config.yaml
# 8. Restart service
# !/etc/init.d/myapp restart
# 9. Monitor logs
# :terminal tail -f /var/log/myapp/error.log
# 10. Confirm fix (2 minutes total)
Time breakdown:
- Open Neovim: <1 second
- Edit: 30 seconds
- Validate: 10 seconds
- Restart: 20 seconds
- Verify: 30 seconds
- Total: ~2 minutes
With VS Code Remote: 15+ minutes (setup connection, wait for initialization, etc.)
Scenario 2: Terraform Multi-Environment Deployment
Task: Deploy new infrastructure to 3 regions simultaneously
Environments:
- prod-us (terraform/aws-us/main.tf)
- prod-eu (terraform/aws-eu/main.tf)
- prod-asia (terraform/aws-asia/main.tf)
The workflow:
# Terminal 1: Start editing
ssh terraform-deploy-server
nvim terraform/
# Neovim opens file browser
# Ctrl+t opens split
# Open 3 files in tabs
# Tab 1: aws-us/main.tf
# Make changes to infrastructure definition
# Space+f β Auto-format
# Space+tv β Validate with terraform
# Tab 2: aws-eu/main.tf
# Make same changes
# Tab 3: aws-asia/main.tf
# Make same changes
# Split window to terminal
:terminal
# In terminal:
terraform plan -out=plan-us terraform/aws-us
terraform plan -out=plan-eu terraform/aws-eu
terraform plan -out=plan-asia terraform/aws-asia
# Review all plans simultaneously in split windows
# If happy:
terraform apply plan-us &
terraform apply plan-eu &
terraform apply plan-asia &
# Watch progress
tail -f /tmp/terraform-apply.log
Advantages:
- All 3 regions edited in one session
- Local keybindings work consistently
- Can see all code simultaneously
- Terminal access for testing/validation
- No file syncing issues
Scenario 3: Debugging Ansible Playbook
Issue: Ansible playbook fails on 10 hosts
Need to: Inspect playbook, edit, test incrementally
The workflow:
ssh ansible-control-server
nvim playbooks/deploy.yml
# In editor:
# - View playbook (syntax highlighting with Treesitter)
# - LSP shows YAML errors before running
# - Cursor over variable name
# - gr β Find all references to this variable
# - gd β Find where defined
# - Edit a task
# Split terminal:
:split
:terminal
# In terminal pane:
ansible-playbook playbooks/deploy.yml --check --limit test-host-01
# See output in real-time
# Go back to editor pane, fix error
# Re-run test
# Iterate until perfect
Advantages:
- Edit and test in same window
- No file transfers needed
- See results immediately
- All in one cohesive workflow
π Part 8: The Learning Curve (Honest Assessment)
Neovim is powerful. It also has a learning curve.
Week 1: The Basics (4-6 hours)
Learn:
- hjkl navigation (move cursor without arrow keys)
- i/I β insert mode
- ESC β normal mode
- :w β save
- :q β quit
- dd β delete line
- yy β copy line
- p β paste
Practice: 30 minutes daily
By end: Can edit files slowly but surely
Week 2: Modal Editing (4-6 hours)
Learn:
- w/b β jump by word
- {/} β jump by paragraph
- /{pattern} β search
- n β next result
- * β search word under cursor
- / $ β end of line
- 0 β beginning of line
- G β end of file
- gg β beginning of file
Practice: 30 minutes daily
By end: Navigation is fast
Week 3: Editing Efficiently (4-6 hours)
Learn:
- ci" β change inside quotes
- da( β delete around parentheses
- v β visual mode
- V β visual line mode
- Ctrl+v β visual block mode
- . β repeat last command
Practice: 30 minutes daily
By end: Editing is faster than mouse clicking
Month 2: Personalization (varies)
Learn:
- Map custom keybindings
- Install LSP for your languages
- Configure formatters
- Build your configuration
By end: Neovim works exactly for YOUR workflow
The Honest Truth
If you only need to:
- Edit files occasionally
- Do basic text editing
- Not invest time learning
Then: VS Code is better
If you are:
- Working with infrastructure daily
- Editing multiple languages
- In SSH environments regularly
- Want fast, minimal workflow
- Willing to invest 20 hours learning
Then: Neovim is worth it (and you'll use it for 20 years)
The payoff:
Month 1: Slower than VS Code (learning)
Month 2: Similar speed to VS Code
Month 3+: Faster than VS Code
Total time investment: 20 hours
Time saved per year (DevOps role): 40+ hours
ROI: Incredibly positive
π Part 9: Integration with DevOps Workflows
Making Neovim part of your actual infrastructure workflow.
Git Workflow Integration
-- In your Neovim config, add Git integration
-- Fugitive (Git commands)
vim.keymap.set('n', '<leader>gs', ':Git status<CR>')
vim.keymap.set('n', '<leader>gd', ':Git diff<CR>')
vim.keymap.set('n', '<leader>ga', ':Git add %<CR>')
vim.keymap.set('n', '<leader>gc', ':Git commit<CR>')
vim.keymap.set('n', '<leader>gp', ':Git push<CR>')
Workflow:
# Edit infrastructure code
nvim terraform/main.tf
# Check git status
<leader>gs
# Add changes
<leader>ga
# Commit with message
<leader>gc
# Push to remote
<leader>gp
# All without leaving Neovim
CI/CD Integration
# Your CI/CD config (.github/workflows/deploy.yml)
- name: Validate Infrastructure
run: |
terraform fmt -check terraform/
terraform validate terraform/
- name: Lint Ansible
run: ansible-lint playbooks/
- name: Lint Shell Scripts
run: shellcheck scripts/*.sh
Local pre-commit hook (before pushing):
#!/bin/bash
# .git/hooks/pre-commit
echo "Running pre-commit checks..."
# Format Terraform
terraform fmt -recursive terraform/
# Validate Terraform
terraform validate terraform/ || exit 1
# Lint Ansible
ansible-lint playbooks/ || exit 1
# Lint shell scripts
shellcheck scripts/*.sh || exit 1
echo "All checks passed!"
Now when you edit in Neovim and save:
1. Auto-format happens (configured in init.lua)
2. Linting happens
3. Visual feedback in editor
4. Fix issues immediately
5. When you git push, pre-commit hook runs
6. CI/CD validates again
Configuration Management Sync
# ~/.local/bin/sync-nvim-config
#!/bin/bash
# Sync Neovim config to all servers
SERVERS=(
"prod-bastion"
"staging-bastion"
"dev-bastion"
)
for server in "${SERVERS[@]}"; do
echo "Syncing Neovim config to $server..."
rsync -az ~/.config/nvim/ $server:~/.config/nvim/
done
echo "Config synced to all servers"
Now your Neovim configuration is consistent across all servers you access:
# On your local machine
./sync-nvim-config
# Now when you SSH to any server:
ssh prod-bastion
# Neovim has identical configuration everywhere
# Same keybindings
# Same plugins
# Same LSPs
# Consistent experience
π Part 10: Comparison Matrix (Neovim vs Alternatives)
Letβs be honest about tradeoffs:
Neovim VS Code Remote JetBrains
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SSH Performance βββββ ββ β
Dependencies βββββ ββ β
Resource Usage βββββ ββ β
Learning Curve ββ βββββ βββ
IDE Features βββ βββββ βββββ
Multi-Language βββ ββββ βββ
Customization βββββ βββ β
Built-in GUI β βββββ βββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Best for:
- Neovim: Infrastructure work, remote SSH, minimal resources
- VS Code: Modern development, GUI features, learning curve
- JetBrains: Language-specific advanced features, enterprise
When to Choose Each
Choose Neovim if:
β
Working primarily via SSH
β
Editing infrastructure code
β
Multiple programming languages
β
Resource-constrained environments
β
Value keyboard efficiency
β
Want to invest in learning
β
Prefer Unix philosophy
Choose VS Code if:
β
Learning to code
β
Prefer GUI over keyboard
β
Want "it works out of box"
β
Mobile development primarily
β
Team uses VS Code extensions
β
Don't SSH frequently
Choose JetBrains if:
β
Language-specific (Python, Go, Rust)
β
Enterprise debugging needed
β
Want maximum IDE features
β
Have budget for licenses
β
Don't work over SSH
β‘ Part 11: Advanced Customization (Beyond Basics)
Once youβre comfortable with Neovim fundamentals, customization transforms it into your perfect tool.
Custom Command Palette
-- ~/.config/nvim/lua/commands.lua
-- Command palette with telescope
require('telescope').load_extension('command_palette')
-- Or create custom commands for DevOps workflows
vim.api.nvim_create_user_command(
'TfPlan',
function()
vim.cmd(':terminal terraform plan')
end,
{ desc = 'Run terraform plan' }
)
vim.api.nvim_create_user_command(
'AnsibleLint',
function(opts)
local file = vim.fn.expand('%')
vim.cmd(':terminal ansible-lint ' .. file)
end,
{ desc = 'Lint current Ansible file' }
)
vim.api.nvim_create_user_command(
'KubectlApply',
function()
vim.cmd(':terminal kubectl apply -f % --dry-run=client')
end,
{ desc = 'Dry-run kubectl apply' }
)
vim.api.nvim_create_user_command(
'ValidateYaml',
function()
vim.cmd(':terminal python3 -m yaml < %')
end,
{ desc = 'Validate YAML syntax' }
)
Usage:
:TfPlan β Run terraform plan
:AnsibleLint β Lint Ansible playbook
:KubectlApply β Dry-run Kubernetes manifest
:ValidateYaml β Validate YAML file
Auto-Commands for Workflow Automation
-- ~/.config/nvim/lua/autocommands.lua
local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd
-- Auto-format on save
augroup('FormatOnSave', { clear = true })
autocmd('BufWritePre', {
group = 'FormatOnSave',
pattern = { '*.tf', '*.py', '*.go', '*.yaml', '*.json' },
callback = function()
vim.lsp.buf.format({ async = false })
end,
})
-- Auto-run linting on save
augroup('LintOnSave', { clear = true })
autocmd('BufWritePost', {
group = 'LintOnSave',
pattern = { '*.tf', '*.py', '*.sh' },
callback = function()
local file = vim.fn.expand('%')
if file:match('%.tf$') then
os.execute('terraform validate ' .. file .. ' &')
elseif file:match('%.py$') then
os.execute('python3 -m py_compile ' .. file .. ' &')
elseif file:match('%.sh$') then
os.execute('shellcheck ' .. file .. ' &')
end
end,
})
-- Show git diff in status line
augroup('GitStatus', { clear = true })
autocmd('BufEnter', {
group = 'GitStatus',
callback = function()
local git_status = vim.fn.system('git status --porcelain ' .. vim.fn.expand('%'))
if git_status ~= '' then
vim.notify('File has uncommitted changes', vim.log.levels.INFO)
end
end,
})
-- Auto-reload file if changed externally
augroup('AutoReload', { clear = true })
autocmd('FocusGained', {
group = 'AutoReload',
command = 'checktime',
})
Custom Snippets for Infrastructure Code
-- ~/.config/nvim/lua/snippets.lua
-- Using LuaSnip
local ls = require('luasnip')
local s = ls.snippet
local t = ls.text_node
local i = ls.insert_node
-- Terraform snippets
ls.add_snippets('terraform', {
-- AWS EC2 instance
s('awsec2', {
t('resource "aws_instance" "'),
i(1, 'name'),
t('" {'),
t('\n ami = "'),
i(2, 'ami-0c55b159'),
t('"'),
t('\n instance_type = "'),
i(3, 't2.micro'),
t('"'),
t('\n '),
t('\n tags = {'),
t('\n Name = "'),
i(4, 'my-instance'),
t('"'),
t('\n }'),
t('\n}'),
}),
-- Kubernetes provider
s('k8sprovider', {
t('terraform {'),
t('\n required_providers {'),
t('\n kubernetes = {'),
t('\n source = "hashicorp/kubernetes"'),
t('\n version = "'),
i(1, '2.16.0'),
t('"'),
t('\n }'),
t('\n }'),
t('\n}'),
t('\n'),
t('\nprovider "kubernetes" {'),
t('\n config_path = "'),
i(2, '~/.kube/config'),
t('"'),
t('\n}'),
}),
})
-- Ansible snippets
ls.add_snippets('yaml', {
s('ansible-task', {
t('- name: '),
i(1, 'Task name'),
t('\n '),
i(2, 'module'),
t(':\n '),
i(3, 'key'),
t(': '),
i(4, 'value'),
}),
s('ansible-handler', {
t('handlers:'),
t('\n - name: '),
i(1, 'Handler name'),
t('\n '),
i(2, 'module'),
t(':\n '),
i(3, 'key'),
t(': '),
i(4, 'value'),
}),
})
Status Line Customization
-- ~/.config/nvim/lua/statusline.lua
-- Using lualine.nvim
require('lualine').setup({
options = {
icons_enabled = true,
theme = 'tokyonight',
component_separators = { left = '|', right = '|' },
section_separators = { left = '', right = '' },
disabled_filetypes = {
statusline = {},
winbar = {},
},
ignore_focus = {},
always_divide_middle = true,
globalstatus = false,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
},
},
sections = {
lualine_a = { 'mode' },
lualine_b = {
{ 'branch', icon = '' },
{ 'diff', symbols = { added = '+', modified = '~', removed = '-' } },
},
lualine_c = {
{ 'filename', path = 1 },
function()
local lsp = vim.lsp.get_active_clients()
if next(lsp) == nil then
return ''
end
return 'π ' .. table.concat(vim.tbl_map(function(c) return c.name end, lsp), ', ')
end,
},
lualine_x = {
{ 'encoding' },
{ 'fileformat' },
{ 'filetype' },
},
lualine_y = { 'progress' },
lualine_z = { 'location' },
},
})
Shows:
- Current mode (Normal, Insert, Visual)
- Git branch and diff status
- Active LSP servers
- File encoding, format, type
- Progress and location
Telescope Configuration (Fuzzy Finder)
-- ~/.config/nvim/lua/telescope-config.lua
require('telescope').setup({
defaults = {
vimgrep_arguments = {
'rg',
'--color=never',
'--no-heading',
'--with-filename',
'--line-number',
'--column',
'--smart-case',
},
prompt_prefix = 'π ',
selection_caret = 'β ',
entry_prefix = ' ',
initial_mode = 'insert',
selection_strategy = 'reset',
sorting_strategy = 'ascending',
layout_strategy = 'horizontal',
layout_config = {
horizontal = {
mirror = false,
preview_width = 0.55,
results_width = 0.8,
},
vertical = {
mirror = false,
},
width = 0.87,
height = 0.80,
preview_cutoff = 120,
},
},
pickers = {
find_files = {
find_command = { 'rg', '--files', '--hidden', '--glob', '!.git' },
},
live_grep = {
theme = 'ivy',
},
buffers = {
show_all_buffers = true,
previewer = true,
},
},
extensions = {
fzf = {
fuzzy = true,
override_generic_sorter = true,
override_file_sorter = true,
case_mode = 'smart_case',
},
},
})
-- Keybindings for Telescope
vim.keymap.set('n', '<leader>ff', ':Telescope find_files<CR>')
vim.keymap.set('n', '<leader>fg', ':Telescope live_grep<CR>')
vim.keymap.set('n', '<leader>fb', ':Telescope buffers<CR>')
vim.keymap.set('n', '<leader>fh', ':Telescope help_tags<CR>')
vim.keymap.set('n', '<leader>fr', ':Telescope lsp_references<CR>')
vim.keymap.set('n', '<leader>fd', ':Telescope lsp_definitions<CR>')
πΉ Part 12: Golang in Neovim (Advanced Development Workflow)
Go is increasingly used in DevOps (Kubernetes, Terraform providers, infrastructure tools). Neovim becomes exceptional for Go development.
Why Go + Neovim is Perfect
Go philosophy:
- Simplicity (one way to do things)
- Fast compilation
- Built-in tooling (gofmt, go vet, etc.)
- Cross-platform compilation
- Static binaries
Neovim philosophy:
- Simplicity (modal editing, composability)
- Minimal dependencies
- Built-in tooling (LSP, terminal, etc.)
- Cross-platform
- Keyboard-centric
These philosophies align perfectly.
Setup gopls (Go Language Server)
-- ~/.config/nvim/after/plugin/lspconfig-go.lua
local lspconfig = require('lspconfig')
lspconfig.gopls.setup({
cmd = { 'gopls', 'serve' },
filetypes = { 'go', 'gomod', 'gowork', 'gotmpl' },
root_dir = function(fname)
local go_root = lspconfig.util.root_pattern('go.work', 'go.mod', '.git')(fname)
return go_root
end,
on_attach = function(client, bufnr)
local opts = { noremap = true, silent = true, buffer = bufnr }
-- Go-specific keybindings
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
vim.keymap.set('n', '<leader>f', vim.lsp.buf.format, opts)
vim.keymap.set('n', '<leader>d', vim.diagnostic.open_float, opts)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
end,
capabilities = require('cmp_nvim_lsp').default_capabilities(),
settings = {
gopls = {
completeUnimported = true,
usePlaceholders = true,
analyses = {
unusedparams = true,
shadow = true,
unusedwrite = true,
},
-- Enable semantic tokens
semanticTokens = true,
-- Enable code lenses
codelens = {
generate = true,
test = true,
gc_details = true,
},
hints = {
assignVariableTypes = true,
compositeLiteralFields = true,
compositeLiteralTypes = true,
constantValues = true,
functionTypeParameters = true,
parameterNames = true,
rangeVariableTypes = true,
},
},
},
})
-- Auto-format on save for Go
vim.api.nvim_create_autocmd('BufWritePre', {
pattern = '*.go',
callback = function()
vim.lsp.buf.format({ async = false })
end,
})
-- Run gofmt + goimports on save
vim.api.nvim_create_autocmd('BufWritePre', {
pattern = '*.go',
callback = function()
local params = vim.lsp.util.make_range_params()
params.context = { only = { 'source.organizeImports' } }
local result = vim.lsp.buf_request_sync(0, 'textDocument/codeAction', params)
for cid, res in pairs(result or {}) do
for _, r in pairs(res.result or {}) do
if r.edit then
vim.lsp.util.apply_workspace_edit(r.edit, vim.lsp.get_client_by_id(cid).offset_encoding)
else
vim.lsp.buf.execute_command(r.command)
end
end
end
end,
})
Go Testing Integration
-- ~/.config/nvim/lua/go-testing.lua
local M = {}
-- Run current test file
function M.run_current_test_file()
vim.cmd(':terminal go test -v ./%')
end
-- Run test under cursor
function M.run_test_under_cursor()
local current_line = vim.fn.line('.')
local current_file = vim.fn.expand('%')
local test_name = vim.fn.matchstr(
vim.fn.getline(current_line),
'func \\(Test\\w*\\)'
)
if test_name ~= '' then
vim.cmd(':terminal go test -v -run ' .. test_name .. ' ./' .. current_file)
else
vim.notify('Not in a test function', vim.log.levels.ERROR)
end
end
-- Run all tests in package
function M.run_all_tests()
vim.cmd(':terminal go test -v ./...')
end
-- Run tests with coverage
function M.run_tests_with_coverage()
vim.cmd(':terminal go test -cover -coverprofile=coverage.out ./...')
vim.cmd(':terminal go tool cover -html=coverage.out')
end
-- Benchmark current test
function M.run_benchmark()
local current_line = vim.fn.line('.')
local bench_name = vim.fn.matchstr(
vim.fn.getline(current_line),
'func \\(Benchmark\\w*\\)'
)
if bench_name ~= '' then
vim.cmd(':terminal go test -bench ' .. bench_name .. ' -benchmem')
else
vim.notify('Not in a benchmark function', vim.log.levels.ERROR)
end
end
-- Setup keybindings
vim.api.nvim_create_augroup('GoTesting', { clear = true })
vim.api.nvim_create_autocmd('FileType', {
group = 'GoTesting',
pattern = 'go',
callback = function()
local opts = { noremap = true, silent = true, buffer = true }
vim.keymap.set('n', '<leader>gt', M.run_current_test_file, opts)
vim.keymap.set('n', '<leader>gT', M.run_test_under_cursor, opts)
vim.keymap.set('n', '<leader>ga', M.run_all_tests, opts)
vim.keymap.set('n', '<leader>gc', M.run_tests_with_coverage, opts)
vim.keymap.set('n', '<leader>gb', M.run_benchmark, opts)
end,
})
return M
Usage:
<leader>gt β Run current test file
<leader>gT β Run test under cursor
<leader>ga β Run all tests in package
<leader>gc β Run tests with coverage
<leader>gb β Run benchmark
Go Debugging with DAP
-- ~/.config/nvim/lua/go-debug.lua
local dap = require('dap')
local dapui = require('dapui')
-- Configure Delve debugger for Go
dap.adapters.go = {
type = 'executable',
command = 'dlv',
args = { 'dap' },
}
dap.configurations.go = {
{
type = 'go',
name = 'Attach',
mode = 'local',
request = 'attach',
processId = require('dap.utils').pick_process,
showLog = false,
},
{
type = 'go',
name = 'Debug',
mode = 'debug',
request = 'launch',
program = '${fileDirname}',
env = {},
args = {},
showLog = false,
},
{
type = 'go',
name = 'Debug Test',
mode = 'test',
request = 'launch',
program = '${fileDirname}',
args = {},
showLog = false,
},
}
-- DAP UI setup
dapui.setup()
dap.listeners.after.event_initialized['dapui_config'] = function()
dapui.open()
end
dap.listeners.before.event_terminated['dapui_config'] = function()
dapui.close()
end
dap.listeners.before.event_exited['dapui_config'] = function()
dapui.close()
end
-- Keybindings
vim.keymap.set('n', '<leader>db', dap.toggle_breakpoint, { noremap = true })
vim.keymap.set('n', '<leader>dc', dap.continue, { noremap = true })
vim.keymap.set('n', '<leader>dn', dap.step_over, { noremap = true })
vim.keymap.set('n', '<leader>di', dap.step_into, { noremap = true })
vim.keymap.set('n', '<leader>do', dap.step_out, { noremap = true })
vim.keymap.set('n', '<leader>dr', dap.repl.open, { noremap = true })
Go Project Structure Navigation
-- ~/.config/nvim/lua/go-nav.lua
-- Quick navigation for Go projects
vim.api.nvim_create_user_command('GoMain', function()
vim.cmd(':edit main.go')
end, { desc = 'Open main.go' })
vim.api.nvim_create_user_command('GoTest', function()
local current = vim.fn.expand('%:t:r')
vim.cmd(':edit ' .. current .. '_test.go')
end, { desc = 'Open corresponding test file' })
vim.api.nvim_create_user_command('GoMod', function()
vim.cmd(':edit go.mod')
end, { desc = 'Open go.mod' })
vim.api.nvim_create_user_command('GoSum', function()
vim.cmd(':edit go.sum')
end, { desc = 'Open go.sum' })
-- Go to implementation (not just definition)
vim.api.nvim_create_user_command('GoImpl', function()
vim.lsp.buf.implementation()
end, { desc = 'Go to interface implementation' })
-- View Go interfaces
vim.api.nvim_create_user_command('GoInterface', function()
vim.cmd(':Telescope lsp_type_definitions')
end, { desc = 'Show interface definitions' })
Go Workflow Example
Scenario: Building a Kubernetes controller
You're editing: cmd/controller/main.go
Workflow:
1. Navigate to main.go
:GoMain
2. View interface definition
:GoInterface
3. Go to implementation
:GoImpl
4. Run tests while editing
<leader>gt (runs go test -v)
5. Set breakpoint and debug
<leader>db (toggle breakpoint)
<leader>dc (continue execution)
<leader>dn (step over)
6. Benchmark performance-critical code
<leader>gb (run benchmarks)
7. Check code coverage
<leader>gc (run tests with coverage)
8. Format and organize imports on save
Auto-runs goimports and gofmt
9. Commit changes
<leader>gs (git status)
<leader>ga (git add)
<leader>gc (git commit)
Advanced Go Analysis
-- ~/.config/nvim/lua/go-analysis.lua
-- Show type information
vim.api.nvim_create_user_command('GoType', function()
vim.lsp.buf.hover()
end, { desc = 'Show type information' })
-- Show method receivers
vim.api.nvim_create_user_command('GoReceiver', function()
local word = vim.fn.expand('<cword>')
vim.cmd(':Telescope lsp_implementations')
end, { desc = 'Show method receivers' })
-- Inspect call hierarchy
vim.api.nvim_create_user_command('GoCallHierarchy', function()
vim.lsp.buf.incoming_calls()
end, { desc = 'Show incoming calls' })
-- Generate interface implementation
vim.api.nvim_create_user_command('GoGenerateImpl', function()
vim.cmd(':terminal go generate ./...')
end, { desc = 'Generate code with go generate' })
-- Run go vet
vim.api.nvim_create_user_command('GoVet', function()
vim.cmd(':terminal go vet ./...')
end, { desc = 'Run go vet' })
-- Analyze goroutine leaks
vim.api.nvim_create_user_command('GoLeakCheck', function()
vim.cmd(':terminal go test -run TestLeaks ./...')
end, { desc = 'Check for goroutine leaks' })
Go + DevOps Integration
Since DevOps increasingly uses Go:
-- ~/.config/nvim/lua/go-devops.lua
-- Build for multiple platforms
vim.api.nvim_create_user_command('GoBuildAll', function()
vim.cmd(':terminal ' ..
'GOOS=linux GOARCH=amd64 go build -o bin/app-linux && ' ..
'GOOS=darwin GOARCH=amd64 go build -o bin/app-mac && ' ..
'GOOS=windows GOARCH=amd64 go build -o bin/app.exe'
)
end, { desc = 'Build for Linux, macOS, Windows' })
-- Build for ARM (Kubernetes nodes)
vim.api.nvim_create_user_command('GoBuildARM', function()
vim.cmd(':terminal GOOS=linux GOARCH=arm64 go build -o bin/app-arm64')
end, { desc = 'Build for ARM64 (Kubernetes nodes)' })
-- Build Docker image
vim.api.nvim_create_user_command('GoDocker', function()
vim.cmd(':terminal docker build -t myapp:latest .')
end, { desc = 'Build Docker image' })
-- Deploy Kubernetes
vim.api.nvim_create_user_command('GoDeployK8s', function()
vim.cmd(':terminal kubectl apply -f k8s/ --dry-run=client -o yaml | kubectl apply -f -')
end, { desc = 'Deploy to Kubernetes' })
π Conclusion: Why Neovim Wins for DevOps
Let me tie this together:
The Core Argument
DevOps work is fundamentally different from application development.
Application development:
- One project
- One language (usually)
- One IDE
- Local machine mostly
- Rich GUI helpful
DevOps work:
- Multiple infrastructure systems
- Multiple languages/tools
- SSH environments frequently
- Resource-constrained systems often
- GUI irrelevant in SSH terminal
Traditional IDEs (VS Code, JetBrains) were designed for application development. Theyβre being adapted for DevOps work, but that adaptation is uncomfortable.
Neovim was designed for exactly the scenario DevOps engineers face: editing code efficiently in terminals, over SSH, with minimal resources.
The Verdict
For DevOps engineers: Neovim is not just a choice. It's the rational choice.
Why?
1. Works perfectly in SSH (your primary work environment)
2. Minimal dependencies (reliability principle)
3. Fast (modal editing is optimized for speed)
4. Handles multiple languages (you use 5+ languages)
5. Scales to 100 servers (you can SSH to any one instantly)
6. Terminal-native (philosophy matches Unix philosophy)
7. Keyboard-centric (your hands never leave keyboard)
8. Highly customizable (your workflow, your config)
The Learning Path Forward
Week 1: Learn basic navigation
(hjkl, i/esc, :w, :q)
Week 2: Learn efficient editing
(word jumps, search, motions)
Week 3: Customize for your languages
(LSP, formatters, keybindings)
Week 4: Integrate with your workflow
(git, terraform, ansible)
Month 2+: Refine and optimize
(you now prefer it to alternatives)
Result: You have a tool you'll use for 20 years
Starting Today
If you want to try Neovim for DevOps:
# Install Neovim
brew install neovim # macOS
# or
apt install neovim # Linux
# Copy the configuration from this guide
# ~/.config/nvim/init.lua
# Install plugin manager
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim
# Start Neovim
nvim
# Inside Neovim:
:PackerInstall
# Give it 20 hours
# You'll understand why DevOps engineers prefer it
The question isnβt whether Neovim is better than VS Code.
The question is: are you willing to invest 20 hours to have a tool specifically optimized for infrastructure work?
For most DevOps engineers, the answer should be yes.
Tags
Related Articles
GitFlow + GitOps: The Complete Senior Git Guide for Agile Teams and Scrum
A comprehensive tutorial on GitFlow and GitOps best practices from a senior developer's perspective. Master branch strategy, conflict resolution, commit discipline, merge strategies, documentation, and how to be a Git professional in Agile/Scrum teams.
Neovim for Go Backend Development: Monorepo, Microservices, Infrastructure
A comprehensive guide to mastering Neovim for large-scale Go backend development. Navigate monorepos, manage microservices, mix Go with infrastructure-as-code, and orchestrate multiple languages seamlessly. Learn how senior Go engineers use Neovim for complex distributed systems.
Neovim: The Complete Guide to Modern Text Editing
A comprehensive guide to Neovim 0.11: installation, configuration, plugins, keybindings, and how to build a powerful development environment for Go and Flutter development. From beginner basics to advanced customization.