Linux CLI Best Practices
Shared portable guidance for Go engineers operating services on Linux hosts, in CI, and inside containers.
Apply these rules in runbooks, onboarding checklists, and code review when shell workflows touch production paths.
How to Use This List
- Tick items as your team standardizes laptops, CI images, and runbooks.
- Treat Tier A rules as mandatory before production SSH or kubectl access.
- Encode test and log capture habits in CI scripts, not tribal knowledge.
- Revisit when you adopt distroless images, new Go toolchain pins, or GitOps-only deploys.
A - Navigation and environment
- Start commands from the module root with
go.modvisible. Wrong working directories produce confusinggo testand relative config errors. - Keep
GOPATH/binandGOROOT/binon PATH in login and non-login shells. CI and cron jobs missgo installtools otherwise. - Set
GOTOOLCHAINpolicy explicitly (autovslocal). Reproducible releases should not surprise-download mid-pipeline. - Scope
GOPRIVATEwith direnv, not global.bashrc. Prevents leaking org module settings into personal repos. - Document architecture when cross-compiling (
GOOS,GOARCH,CGO_ENABLED). Ship the ELF binary your server actually runs.
B - Process and signals
- Prefer
SIGTERMand graceful shutdown beforekill -9. Validates GoShutdownpaths you rely on in systemd and Kubernetes. - Identify processes with
pgrep/ssbefore restarting services. Avoid killing the wrong binary on shared hosts. - Set
LimitNOFILE(or ulimits) for high-connection Go servers. Default 1024 exhausts under moderate HTTP concurrency. - Align systemd
TimeoutStopSecwith in-process shutdown timeouts. Prevents SIGKILL mid-request drain. - Run long-running commands under tmux on SSH hosts. Dropped connections should not abort
go test ./...evidence gathering.
C - Logs and search
- Log to stdout/stderr in containers and supervised services. Platforms ship journal/kubelet logs, not files inside ephemeral disks.
- Use JSON field names stable enough for
jqfilters.trace_id,level, andmsgshould not rename per release without notice. - Search code with ripgrep defaults, not unscoped
grep -R. Respect.gitignoreto skipvendor/and build artifacts. - Capture command output with
teeduring incidents. Pipes alone lose evidence when scrollback truncates. - Correlate app logs with
journalctl --sincewindows. Kernel OOM and unit restart lines explain silent Go process exits.
D - Build, test, and profile
- Run
go test ./... -count=1before pushing when CI does the same. Cache hides flakes locally. - Enable
-racein CI for pure Go packages unless documented otherwise. Data races are cheaper to fix pre-prod. - Store profiles and test logs in
artifacts/with timestamps. Supports postmortems and perf comparisons (pprof -base). - Bind debug pprof to loopback or port-forward only. Open
:6060on0.0.0.0is a known exposure. - Profile under representative load, not idle servers. Empty CPU profiles waste incident time.
E - systemd and VMs
- Run services as dedicated non-root users. Go does not require root except special bind cases - use proxies or capabilities.
- Keep secrets out of world-readable unit files. Use restricted
EnvironmentFileor secret agents. - Use
Restart=on-failurewithRestartSecbackoff. Crash loops should not hammer databases on startup. - Version binaries under
/opt/<app>/binwith atomic symlink swaps. Supports rollback without editing unit files. - Test
ExecStartmanually as the service user before enable. Catches permission and path errors early.
F - Containers and Kubernetes
- Use multi-stage Dockerfiles: compile with Go image, run minimal runtime. Distroless or static images shrink attack surface.
- Add
.dockerignoreexcluding.git, tests, and local artifacts. Image size and cache churn stay predictable. - Check
kubectl describeEvents for probe and exit codes. Logs alone missCrashLoopBackOffroot causes. - Use
kubectl logs --previousafter crashes. Current container logs may be empty on instant failure. - Close
kubectl port-forwardafter debugging. Tunnels bypass policy you assume protects admin ports.
G - Productivity and safety
- Allow direnv only after reviewing
.envrcdiffs. Prevents malicious env injection on shared clones. - Avoid pasting production credentials into shell history. Prefer scripts reading from vault files or env injected at runtime.
- Pin tool versions in runbooks (
kubectl,docker,jq). CLI flag differences break copy-paste during incidents. - Prefer reproducible scripts over one-off hero commands. Postmortems should replay exact steps.
- Redact PII before sharing log excerpts in tickets. jq extracts are not automatic anonymization.
FAQs
Should every Go developer learn systemd?
Anyone shipping to Linux VMs should read unit files and journalctl.
Kubernetes-only teams still benefit from signal and logging concepts systemd illustrates.
Are these practices different on macOS dev laptops?
Most apply conceptually.
Replace journalctl with local log files and validate Linux-specific paths before production SSH.
What is the highest-impact rule for incidents?
Capture logs and command output with timestamps before restarting pods or services.
Restarts destroy the first minute of evidence.
Should CI images include ripgrep and jq?
Yes for debug jobs and log artifact analysis.
Runtime images can stay minimal if debug tools run only in CI workers.
How do these relate to Go CLI tool design?
Operational habits (stdout vs stderr, exit codes) mirror the CLI Tools section.
Align operator scripts with how your Go binaries behave.
When is kill -9 acceptable?
When graceful shutdown hangs past policy timeout and traffic is already drained elsewhere.
Document the exception in runbooks.
Do I need tmux if CI runs all tests?
Local and staging debugging still benefits.
CI does not replace interactive exploration on bastion hosts.
How often should we refresh this checklist?
Review quarterly or when Go minor version, base image, or orchestrator policy changes.
Should juniors get kubectl access?
Read-only RBAC plus documented commands reduces risk.
Pair access with this checklist in onboarding.
What about Windows developers?
WSL2 covers most Linux CLI practice.
Production alignment still requires testing on Linux CI images.
Can we automate checkbox items?
Yes.
Lint CI for tee artifacts, direnv in devcontainer, systemd unit validation with systemd-analyze verify.
Where do cloud-only teams focus?
Prioritize sections C, D, and F.
systemd rules matter when you still SSH to nodes or use VM-based build agents.
Related
- Linux CLI Skills for Go Engineers - section overview
- Linux CLI Basics - hands-on starting examples
- Build, Test & Profile from the Shell - test and pprof workflows
- systemd & Service Unit Files for Go Binaries - production units
- Containers from the CLI: docker & kubectl - cluster debugging
- Shell Productivity: tmux, fzf & direnv - terminal workflow
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).