Cheatsheet

Dev Cheatsheet

A no-fluff reference for day-to-day development.

The index on the left lists the top-level sections; each is split into smaller groups below. Hover any block and hit Copy, and replace anything in <angle-brackets> with your own values.

Git

The everyday loop — branch, commit, rebase, and the occasional rescue.

Everyday flow

git clone <repo-url>            # grab a repository
git checkout -b <branch-name>   # create + switch to a branch
git add .                       # stage everything
git commit -m "message"         # commit staged changes
git pull origin main            # update your branch from remote
git push origin <branch-name>   # publish your branch
git status                      # what's changed
git log --oneline --graph       # compact history with a graph

Fetch & sync

fetch downloads remote commits without touching your working branch; pull is fetch + integrate. How pull integrates is controlled by one config flag:

git fetch <remote>              # download remote refs, don't merge
git fetch --all --prune         # fetch every remote, drop deleted branches
git pull                        # fetch + integrate the current branch
git pull --rebase               # integrate by rebasing, just this once

# Set the default strategy for `git pull`:
git config --global pull.rebase false   # merge  (default)
git config --global pull.rebase true    # rebase
git config --global pull.ff only        # fast-forward only

When to pick which:

  • pull.rebase false (merge) — safe default; records a merge commit when histories diverge. Fine for shared branches, but the log grows merge bubbles.
  • pull.rebase true (rebase) — replays your local commits on top of upstream for a clean, linear history. Great solo or before pushing; avoid rebasing commits you’ve already shared.
  • pull.ff only — refuses to auto-merge or rebase; forces you to decide, so you never get a surprise merge commit.

Rebase

git rebase main                 # replay your branch on top of main
git rebase -i HEAD~3            # interactively squash/edit last 3 commits
git rebase --continue           # after resolving conflicts
git rebase --abort              # bail out and restore the branch

Cherry-pick & reset

git cherry-pick <commit>        # apply one commit onto the current branch
git cherry-pick <a>^..<b>       # apply a range of commits (a..b inclusive)
git cherry-pick --continue      # after resolving conflicts
git cherry-pick --abort         # cancel and restore

git reset --soft HEAD~1         # move HEAD back, keep changes staged
git reset --mixed HEAD~1        # move HEAD back, unstage changes (default)
git reset --hard <commit>       # move HEAD back, discard all changes (careful)
git reset <file>                # unstage a file, keep its changes
git revert <commit>             # new commit that undoes an existing one

Stash & clean

git stash                       # shelve uncommitted changes
git stash pop                   # bring them back
git stash list                  # see stashed entries
git restore <file>              # discard unstaged changes to a file
git clean -df                   # delete untracked files + directories

Remotes

git remote -v                              # list remotes
git remote add <name> <repo-url>           # add a remote
git remote set-url origin <new-url>        # repoint origin
git fetch --all --prune                    # refresh remotes, drop stale branches

Branches

git branch                      # list local branches
git switch <branch>             # switch branches (modern checkout)
git switch -c <branch>          # create + switch
git checkout -                  # jump back to the previous branch
git branch -d <branch>          # delete a merged branch
git branch -D <branch>          # force-delete a branch
git branch -m <new-name>        # rename the current branch
git push -u origin <branch>     # push + set upstream tracking

Inspect & rescue

git diff                        # unstaged changes
git diff --staged               # staged changes (what you're about to commit)
git show <commit>               # what a single commit changed
git log -p <file>               # a file's history, with diffs
git blame <file>                # who last touched each line
git reflog                      # every HEAD move — your safety net
git reset --hard <reflog-ref>   # recover a "lost" commit or branch
git bisect start                # binary-search for the commit that broke things
git tag -a v1.0 -m "release"    # create an annotated tag

Oh My Zsh git aliases

Shortcuts from the Oh My Zsh git plugin — the ones I type all day:

gst        # git status
gco        # git checkout
gcb        # git checkout -b   (new branch)
gcm        # git checkout the main branch
ga         # git add
gaa        # git add --all
gcmsg      # git commit -m
gc!        # git commit --amend
gd         # git diff
glog       # git log --oneline --decorate --graph
ggpush     # git push origin <current-branch>
ggpull     # git pull origin <current-branch>
grbi       # git rebase -i
gstp       # git stash pop

Files & Processes

The shell muscle memory.

Files & directories

mkdir -p path/to/dir             # create nested directories
cp -r <src> <dest>               # copy recursively
mv <src> <dest>                  # move / rename
rm -rf <path>                    # delete recursively (careful)
ln -s <target> <link>            # create a symlink
ls -lah                          # detailed, human-readable listing
tree -L 2                        # directory tree, 2 levels deep
du -sh *                         # size of each item here
df -h                            # free disk space by mount

Find files

find . -name "*.log"                          # by name
find . -type f -mtime -1                       # modified in the last day
find . -size +100M                             # larger than 100 MB
find . -name "*.tmp" -delete                   # find and delete
find . -name "*.rb" -exec grep -l TODO {} +    # run a command on matches
find . -name "*log" -exec truncate --size 0 {} \;   # empty every log file in place

Permissions

chmod +x <file>                  # make a script executable
chmod 644 <file>                 # rw for owner, read for others
chmod -R 755 <dir>               # recurse into a directory
chown -R <user>:<group> <path>   # change ownership

Archives & compression

tar czf out.tar.gz <dir>         # create a gzipped tarball
tar xzf out.tar.gz               # extract one
tar tzf out.tar.gz               # list contents without extracting
zip -r out.zip <dir>             # create a zip
unzip out.zip -d <dir>           # extract into a directory

Search & text

grep -rn "<text>" .              # recursive search with line numbers
grep -i "<text>" <file>          # case-insensitive
tail -f <file>                   # follow a file as it grows
less <file>                      # page through a file (q to quit)
head -n 50 <file>                # first 50 lines
wc -l <file>                     # count lines
sort <file> | uniq -c            # count unique lines
sed -i '' 's/old/new/g' <file>   # in-place find/replace (macOS)
awk '{print $1}' <file>          # print the first column
<cmd> | xargs <other-cmd>        # feed output as arguments

JSON & YAML (jq / yq)

curl -s <url> | jq .             # pretty-print JSON
jq '.items[].name' file.json     # extract a field
yq '.services.web.image' file.yml  # query YAML
yq -i '.version = "2"' file.yml  # edit YAML in place

Processes & ports

ps aux | grep <name>             # find a process
pgrep -fl <name>                 # find PIDs by name
kill <pid>                       # ask a process to stop (SIGTERM)
kill -9 <pid>                    # force-kill (SIGKILL)
pkill -f <pattern>               # kill by command pattern
lsof -i :<port>                  # what's listening on a port
htop                             # interactive process viewer

Environment

export VAR=value                 # set an env var for this shell
echo $VAR                        # print a variable
env | grep <name>                # list matching env vars
which <cmd>                      # path of an executable
source ~/.zshrc                  # reload your shell config

Homebrew & macOS

Keep packages and the shell healthy.

# Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

brew update && brew upgrade      # refresh + upgrade everything
brew doctor                      # diagnose problems
brew install <package>           # install
brew uninstall <package>         # remove
brew list                        # what's installed

Oh My Zsh + handy plugins

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

git clone https://github.com/zsh-users/zsh-autosuggestions \
  ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting \
  ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

Add them to plugins=(...) in ~/.zshrc, then source ~/.zshrc.

SSH

Keys, config, tunnels, and moving files between machines.

Generate & copy

ssh-keygen -t ed25519 -C "you@example.com"   # create a key (name it per host)
chmod 600 ~/.ssh/id_ed25519                  # private key perms
chmod 644 ~/.ssh/id_ed25519.pub              # public key perms
cat ~/.ssh/id_ed25519.pub | pbcopy           # copy public key (macOS)

Paste the public key into GitHub / GitLab / Bitbucket → SSH keys.

Per-host config

Keep different keys for different hosts in ~/.ssh/config:

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes

Host bitbucket.org
  HostName bitbucket.org
  User git
  IdentityFile ~/.ssh/bb_ed25519
  IdentitiesOnly yes

Manage the agent

ssh-add -l                       # list loaded keys
ssh-add ~/.ssh/id_ed25519        # load a key
ssh-add -D                       # remove all loaded keys
ssh -T git@github.com            # test the connection

Connect & run

ssh <user>@<host>                       # open a shell
ssh <user>@<host> -p 2222               # non-default port
ssh <user>@<host> "df -h"               # run one command and return
ssh-copy-id <user>@<host>               # install your public key on a server
ssh -J <jump-host> <user>@<host>        # hop through a bastion / jump host
ssh -v <user>@<host>                    # verbose — debug auth/connection issues

Tunnels & port forwarding

# Local forward: reach a remote service as if it were local.
# e.g. a DB on the server's localhost:5432 -> your localhost:5432
ssh -L 5432:localhost:5432 <user>@<host>

# Remote forward: expose your local service on the remote host.
# e.g. share your local :3000 as the server's :8080
ssh -R 8080:localhost:3000 <user>@<host>

# Dynamic forward: a local SOCKS proxy that tunnels all traffic through the host.
ssh -D 1080 <user>@<host>

# Add -N (no shell) and -f (background) for a forward-only tunnel:
ssh -fNL 5432:localhost:5432 <user>@<host>

-L = Local (pull a remote port to you), -R = Remote (push a local port out), -D = Dynamic (a SOCKS proxy on the given port).

Copy files (scp / rsync)

scp <file> <user>@<host>:<path>             # copy a file to a server
scp <user>@<host>:<path>/file .             # copy from a server
scp -r <dir> <user>@<host>:<path>           # copy a directory

rsync -avz <dir>/ <user>@<host>:<path>/     # sync a tree (only changed files)
rsync -avz --delete <dir>/ <user>@<host>:<path>/   # mirror, removing extras
rsync -avz -e "ssh -p 2222" <src> <dst>     # rsync over a non-default port

Networking & HTTP

Hit APIs, check what’s listening, and chase down DNS.

HTTP requests (curl)

curl <url>                                   # GET, print the body
curl -i <url>                                # include response headers
curl -L <url>                                # follow redirects
curl -O <url>                                # save with the remote filename
curl -H "Authorization: Bearer <token>" <url>   # send a header
curl -X POST -H "Content-Type: application/json" \
  -d '{"key":"value"}' <url>                 # POST JSON
curl -s <url> | jq .                         # pretty-print a JSON API
wget <url>                                   # download a file

Connectivity & DNS

ping <host>                      # is it reachable?
dig +short <domain>              # DNS records (short form)
nslookup <domain>                # DNS lookup
traceroute <host>                # the path packets take
nc -vz <host> <port>             # test whether a TCP port is open

Ports & interfaces

lsof -i :<port>                  # what's using a port
lsof -nP -iTCP -sTCP:LISTEN      # all listening TCP sockets
ipconfig getifaddr en0           # your LAN IP (macOS)
curl -s ifconfig.me              # your public IP

Ruby & Rails

Version managers, dependencies, the run stack, and migrations.

mise

mise is a polyglot version + tool manager (a fast successor to asdf) — one mise.toml pins every language and tool a project needs:

mise use -g ruby@3.3.7           # set a global Ruby
mise use ruby@3.3.7              # pin a tool for this project (writes mise.toml)
mise use -g node@lts python@3.12 # manage several languages at once
mise install                     # install everything declared in mise.toml
mise ls                          # installed tools + versions (+ which file requested them)
mise ls-remote ruby              # list installable versions of a tool
mise trust                       # trust a project's mise.toml
mise exec -- <cmd>               # run a command with mise's tools on PATH
mise run <task>                  # run a task defined in mise.toml
mise upgrade                     # update installed tools

It manages far more than language runtimes. Languages like ruby, node, python, rust, go; CLI tools like terraform, packer, sops, age, gitleaks, direnv, yq, overmind, and even yarn — all pinned per project and kept off your global PATH. Plug-in backends reach packages that aren’t in the core registry:

mise use -g npm:gitlab-ci-local      # install from npm
mise use -g cargo:fracturedjson      # build from a Rust crate
mise use -g github:akiomik/mado      # download a GitHub release
mise use -g aqua:pipx:<pkg>          # other backends: aqua, pipx, go, etc.
mise registry                        # browse everything mise can install

Ruby (rbenv / rvm)

# rbenv
rbenv install -l                 # list installable versions
rbenv install 3.3.0
rbenv global 3.3.0               # set the default
rbenv local 3.3.0                # pin per-project (.ruby-version)
rbenv versions

# rvm
rvm list known                   # installable versions
rvm install 3.3.0
rvm use 3.3.0 --default          # install + set the default
rvm gemset list                  # gemsets for the current Ruby

Node (nvm / npm)

# nvm — manage Node versions
nvm install --lts
nvm install <version>
nvm use <version>
nvm alias default <version>
nvm ls

# npm — Node's package manager
npm install                      # install deps from package.json
npm install <pkg>                # add a dependency
npm install -D <pkg>             # add a dev dependency
npm run <script>                 # run a package.json script
npm ls                           # list installed packages

Bundler & gems

bundle install                   # install gems from the Gemfile
bundle update <gem>              # bump a single gem
bundle exec <command>           # run within the bundle
gem pristine --all              # restore installed gems
gem cleanup                     # remove old gem versions

Run the stack

bin/rails s -p 3000              # start the server
bin/rails c                      # console
redis-server                     # start Redis
overmind start                   # start every Procfile process
overmind connect <process>       # attach to one process (e.g. web, workers)
overmind restart <process>       # restart a single process
bundle exec sidekiq -C config/sidekiq.yml          # background jobs (Sidekiq)
bundle exec rake resque:work QUEUE=*               # background jobs (Resque)

Generators & migrations

bin/rails g migration <Name>                 # create a migration
bin/rails g scaffold <Name> field:type       # scaffold a resource
bin/rails db:setup                           # create + load schema + seed
bin/rails db:migrate                         # run migrations
bin/rails db:migrate:status                  # see migration state
bin/rails db:rollback                        # undo the last migration
bin/rails db:migrate:down VERSION=<ts>       # revert a specific migration
bin/rails db:seed                            # load seeds

Rake tasks

bundle exec rake -T                  # list available tasks
bundle exec rake <task>              # run a task
bundle exec rake "<task>[arg1,arg2]" # run a task with arguments

Testing

RSpec for unit/integration specs, Cucumber for feature specs.

RSpec

bundle exec rspec                            # run the whole suite
bundle exec rspec spec/models/user_spec.rb   # one file
bundle exec rspec spec/models/user_spec.rb:42  # one example at a line
bundle exec rspec --tag focus                # only tagged examples
bundle exec rspec --fail-fast                # stop at the first failure
bundle exec rspec --only-failures            # rerun what failed last time

Cucumber

cucumber                                 # run all features
cucumber features/login.feature          # one feature
cucumber features/login.feature:42       # the scenario at a line
cucumber --tags @wip                     # run tagged scenarios
cucumber -f progress                     # compact dot output

Node & Yarn

Front-end dependencies and build scripts.

yarn install                 # install deps from package.json
yarn add <pkg>               # add a dependency
yarn add -D <pkg>            # add a dev dependency
yarn remove <pkg>            # remove a dependency
yarn upgrade <pkg>           # upgrade a package
yarn why <pkg>               # explain why a package is installed
yarn <script>                # run a package.json script (e.g. yarn build)
yarn cache clean             # clear the Yarn cache

MySQL

User management, dumps, and a password reset that always saves the day.

Users & privileges

CREATE USER '<user>'@'localhost' IDENTIFIED BY '<password>';
GRANT ALL PRIVILEGES ON *.* TO '<user>'@'localhost';
FLUSH PRIVILEGES;
SELECT user, host FROM mysql.user;
DROP USER '<user>'@'localhost';

Dump & restore

mysqldump -u <user> -p <database> > backup.sql   # export
mysql -u <user> -p <database> < backup.sql        # import
gunzip -c backup.sql.gz | mysql -u <user> -p <database>   # import gzipped

Reset the root password

# 1. Stop MySQL, then start it bypassing auth
sudo mysqld_safe --skip-grant-tables &

# 2. In another shell
mysql -u root
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY '<new-password>';

Docker

Build, run, inspect, and ship images.

Containers & images

docker ps -a                     # all containers (running + stopped)
docker images                    # local images
docker build -t <name>:<tag> .   # build from the Dockerfile here
docker run -it -d <name>:<tag>   # run detached, interactive
docker exec -it <container> bash # shell into a running container
docker logs -f <container>       # follow logs
docker stats                     # live CPU / memory per container
docker stop <container>          # stop it
docker rm <container>            # remove a container
docker rmi <image>               # remove an image

Publish an image

docker commit <container> <user>/<name>:<tag>   # snapshot a container
docker push <user>/<name>:<tag>                 # push to a registry
docker pull <user>/<name>:<tag>                 # pull it elsewhere

Compose

docker compose up -d             # start the stack in the background
docker compose ps                # service status
docker compose logs -f <service> # follow one service
docker compose stop              # stop everything
docker compose exec <service> sh # shell into a service
docker compose up -d <service>   # start specific services only

Clean up

docker system df                 # where space is going
docker system prune -af          # remove unused data (careful)
docker volume prune              # drop dangling volumes

Kamal Deploys

Kamal builds an image, pushes it to a registry, and runs it behind kamal-proxy on a target host. Destinations live in config/deploy.<dest>.yml — I keep one per environment (e.g. local for Colima, lima-dev for a Lima VM).

Deploy

kamal server bootstrap -d <dest>   # one-time: install kamal-proxy on the host
kamal deploy -d <dest>             # build + push + deploy
kamal deploy -d <dest> --skip-push # redeploy current image, no rebuild
kamal build push -d <dest>         # build + push the image only (no deploy)
kamal rollback -d <dest>           # roll back to the previous image
kamal app stop -d <dest>           # stop the app containers

Logs & status

kamal app logs -d <dest>           # last N lines
kamal app logs -d <dest> --tail    # follow live logs
kamal app details -d <dest>        # running containers + image SHA

Console & one-off commands

kamal app exec -d <dest> -i -- bundle exec rails console
kamal app exec -d <dest> -- bundle exec rails db:migrate
kamal app exec -d <dest> -- bundle exec rake db:seed
kamal app exec -d <dest> -- bundle exec rails runner "puts Rails.env"
kamal app exec -d <dest> -- curl -s http://localhost:3000/up   # health check

Proxy

kamal proxy boot -d <dest>         # start kamal-proxy (first time)
kamal proxy details -d <dest>      # proxy container status
kamal proxy logs -d <dest>         # proxy logs

Lima & Colima VMs

Local Linux VMs that host the Docker engine my Kamal targets deploy onto.

Lima lifecycle

limactl list                       # all VMs + SSH port + status
limactl start <vm>.yaml            # create + start from a config (first time)
limactl start <vm-name>            # restart an existing VM
limactl stop <vm-name>             # graceful stop
limactl stop --force <vm-name>     # hard stop
limactl delete <vm-name>           # delete the VM (destroys its disk)

Shell & SSH into a VM

limactl shell <vm-name>            # interactive shell inside the VM
limactl shell <vm-name> -- <cmd>   # run one command and return
limactl show-ssh <vm-name>         # print the ssh config block
limactl list --format '{{.SSHLocalPort}}' <vm-name>   # just the SSH port

Dependency stack inside the VM

Bring up the backing services (database, cache, search) with Compose:

limactl shell <vm-name> -- bash -c "cd <app-dir> && docker compose \
  -f deploy/docker/compose.yaml \
  --env-file deploy/docker/.env.<dest> \
  --project-name <project> \
  up -d mysql valkey meilisearch"

Colima

colima start                       # start the default VM
colima stop <profile>              # stop a profile
colima status                      # show status
colima delete <profile>            # delete a profile

Tear down & rebuild

# Remove app + proxy, then destroy the VM (wipes all data)
kamal app stop -d <dest>
kamal proxy remove -d <dest>
limactl stop <vm-name>
limactl delete <vm-name>

# Bring it back from scratch
limactl start <vm>.yaml
source ~/.zshrc