Ship, Securely, Ch. 1: Getting Started
Part of Ship, Securely, a hands-on DevSecOps book.
This chapter does two things. First it explains the idea the rest of the book runs on: security built into the pipeline, not bolted on at the end. Then it gets your machine ready to practice it, with Go, Make, an editor, Docker, and a local Kubernetes cluster to deploy into.
Security in the pipeline, not after it
DevOps is a simple idea with a lot of machinery behind it. Development and operations work as one team, and software ships in small, frequent changes that flow through an automated pipeline: build, test, release, repeat. The pipeline is the product. Code only counts once it is running somewhere, so everything that gets it there is scripted, versioned, and repeatable.
Two habits make that work. Continuous integration is merging small changes often, with a build-and-test step that runs on every push and answers one question: does it still work? Continuous delivery extends that automation past the merge, so a change that passes its checks is always ready to ship, or ships on its own.
DevSecOps is that same pipeline with security built into it. Security stops being a gate someone waves you through at the end and becomes a set of checks that run alongside your tests, on the same push. That is the whole move, and it pays for itself the way every control in this book does: a problem caught the moment it is introduced is the cheapest it will ever be to fix.
In CI: feedback a developer can act on
Where code enters the system, security belongs next to compilation and unit tests, fast enough to run on every push:
- SAST reads your source for insecure patterns.
- SCA checks your dependencies against known CVEs and licenses.
- Secret scanning catches a key or token before it ever leaves the laptop.
Run the fast versions on every push and keep the deep scans on a schedule. The point is to get the feedback while the change is still in your hands.
In CD: guard what you are about to ship
Where artifacts and environments move toward production, the checks shift to the thing being released:
- Scan the container image before you promote it.
- Check the Terraform, Helm, and Kubernetes manifests for risky defaults, like a public bucket or a privileged pod.
- Enforce least privilege, TLS, and only the ports you actually need.
- Keep a canary or blue-green rollout and an automated rollback for when something degrades.
Run it on your own machine first
The thread through the whole book is local-first. Whatever CI and CD will do, do it on your laptop before you push. A Makefile runs the same make test, make lint, and make scan the server will, and a local cluster like Minikube lets you apply manifests, run a smoke test, and rehearse a rollback without touching shared infrastructure. The feedback loop shrinks to seconds, and you learn by doing instead of waiting on a pipeline. The rest of this chapter installs the tools that make that possible.
Pre-requisites
You’ll need the following to work along to the book:
- Docker
- Minikube
- GO
- JQuery
- Make
- VSCODE
I’ve included steps for each one in case you don’t already have it.
Otherwise, feel free to go to the next section.
How to Install Docker (Mac • Windows • Linux)
macOS
Docker Desktop (recommended)
brew install --cask docker # or download Docker Desktop DMG
open -a Docker # start it once to initialize
docker --version
Sanity test (first pull is ~tens of MB):
docker run --rm hello-world
Windows
Docker Desktop (WSL 2 backend)
- Ensure WSL 2 is on:
wsl --install
- Install Docker Desktop:
winget install -e --id Docker.DockerDesktop
- Launch Docker Desktop → Settings → Use WSL 2 based engine (default).
- Test:
docker --version
docker run --rm hello-world
If corporate policy blocks Docker Desktop, alternatives: Rancher Desktop or Podman Desktop (works with this repo; commands are nearly identical).
Linux
Docker Engine (quick path)
Debian/Ubuntu
sudo apt update sudo apt install -y docker.io sudo systemctl enable --now docker sudo usermod -aG docker $USER # then log out/in or run: newgrp docker docker --version docker run --rm hello-worldFedora
sudo dnf install -y docker sudo systemctl enable --now docker sudo usermod -aG docker $USER docker --version docker run --rm hello-world
How to Install GO (Mac • Windows • Linux)
macOS
Option A — Official pkg (recommended)
- Download “macOS pkg installer” from go.dev/dl
- Run the installer (works on Intel & Apple Silicon).
- Verify:
go version
Option B — Homebrew
brew install go
go version
(Optional) Set up your Go workspace
# Add to ~/.zshrc (or ~/.bashrc)
echo 'export GOPATH="$HOME/go"' >> ~/.zshrc
echo 'export GOBIN="$GOPATH/bin"' >> ~/.zshrc
echo 'export PATH="$PATH:$GOBIN"' >> ~/.zshrc
source ~/.zshrc
Windows
Option A — Winget (fastest)
winget install -e --id GoLang.Go
go version
Option B — Chocolatey
choco install golang -y
go version
Option C — Official MSI
- Download the Windows installer from go.dev/dl and run it.
- Open a new PowerShell or Command Prompt:
go version
(Optional) GOPATH
- By default on Windows,
GOPATHisC:\Users\<You>\go. - To use
GOBINon your PATH:
setx GOPATH "$env:USERPROFILE\go"
setx GOBIN "$env:GOPATH\bin"
# Add to PATH for the current session:
$env:Path += ";$env:GOBIN"
Linux
Debian/Ubuntu (apt)
sudo apt update
sudo apt install -y golang
go version
Fedora
sudo dnf install -y golang
go version
Arch
sudo pacman -S --needed go
go version
(Optional) Official tarball (all distros)
# Replace X.Y.Z with the latest version for your architecture
sudo rm -rf /usr/local/go
curl -L https://go.dev/dl/goX.Y.Z.linux-amd64.tar.gz | sudo tar -C /usr/local -xz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
go version
Quick sanity test
mkdir -p ~/tmp/hello && cd ~/tmp/hello
cat > main.go << 'EOF'
package main
import "fmt"
func main(){ fmt.Println("hello, ship-securely") }
EOF
go run .
Common hiccups
Open a new shell after install so your PATH updates.
If
go: command not found, ensure Go’sbindirectory is on PATH:- macOS pkg:
/usr/local/go/bin(Intel) or/opt/homebrew/opt/go/libexec/bin(Brew) - Linux tarball:
/usr/local/go/bin - Windows MSI/winget sets PATH automatically; reopen the terminal.
- macOS pkg:
How to Install JQuery (JQ) (Mac • Windows • Linux)
Installing JQuery
These scripts use jq if present: macOS: brew install jq Windows: winget install -e –id jqlang.jq (or choco install jq) Ubuntu: sudo apt install -y jq Fedora: sudo dnf install -y jq The scripts still run without jq—they’ll just show zeros/“n/a” for a couple metrics.
How to Install Make (Mac • Windows • Linux)
macOS
Option A — Fastest (Xcode Command Line Tools)
xcode-select --install
make --version
This installs Apple’s
make(BSD). It’s fine for our Makefile.
Option B — GNU make via Homebrew (optional)
brew install make
gmake --version
Homebrew installs it as
gmake. If you wantmaketo point to GNU make:
echo 'alias make=gmake' >> ~/.zshrc && source ~/.zshrc
Windows
Pick one of these:
Option A — WSL (recommended)
- Enable WSL and install Ubuntu:
wsl --install
# reboot when prompted
- In the Ubuntu terminal:
sudo apt update && sudo apt install -y build-essential
make --version
Option B — Chocolatey (native)
Set-ExecutionPolicy Bypass -Scope Process -Force
choco install make -y
make --version
Option C — Scoop (native)
iwr -useb get.scoop.sh | iex
scoop install make
make --version
Option D — MSYS2 (native)
- Install MSYS2, then in “MSYS2 MSYS” terminal:
pacman -Syu
pacman -S make
make --version
If you use Git Bash, prefer MSYS2 or WSL—Git Bash alone does not include
make.
Linux
Debian/Ubuntu
sudo apt update && sudo apt install -y build-essential make --versionFedora
sudo dnf install -y make gcc make --versionArch
sudo pacman -S --needed base-devel make --version
How to Install Minikube (Mac • Windows • Linux)
Minikube Setup (for make ci + make cd)
0) Prereqs
- Docker Desktop (Mac/Windows) or Docker Engine (Linux)
- Minikube
- Make (Windows: use Git Bash or install via Chocolatey/Scoop)
- (Optional) cosign if you want to sign/verify images
Verify:
docker --version
minikube version
kubectl version --client
make -v
1) Install Minikube
macOS
brew install minikube
Windows (PowerShell as Admin)
choco install minikube
# or: winget install minikube
Linux (Debian/Ubuntu example)
sudo apt-get update && sudo apt-get install -y curl conntrack
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
2) Start a local cluster
We use a dedicated profile called ship so it doesn’t collide with other setups.
Recommended driver: Docker (works on Mac/Windows/Linux)
minikube start -p ship --driver=docker --cpus=2 --memory=4096
minikube -p ship status
Windows (Hyper-V alternative, if Docker Desktop isn’t available):
minikube start -p ship --driver=hyperv --cpus=2 --memory=4096
Tip: If you’ve got 16GB+ RAM, bump to
--cpus=4 --memory=6144for a smoother experience.
3) Point kubectl at Minikube & create the namespace
kubectl config use-context minikube
kubectl get nodes
kubectl create namespace ship || true
4) Warm the cache (optional but recommended)
Pre-pull common tool images so the class is fast even offline:
docker pull returntocorp/semgrep:latest
docker pull zricethezav/gitleaks:latest
docker pull bridgecrew/checkov:latest
docker pull hadolint/hadolint:latest
docker pull aquasec/trivy:latest
docker pull anchore/syft:latest
docker pull zaproxy/zap-stable:latest
(Or use the warm-cache target below.)
5) Run it
From the repo root:
make ci # builds & scans locally -> artifacts/
make cd # starts Minikube (if needed), loads image, applies K8s, verifies
make k8s-port # prints a URL to hit the app via Minikube
Open the printed URL (e.g., http://127.0.0.1:xxxxx) and try:
//healthz/echo?input=<script>alert(1)</script>(intentional XSS for the lab)
6) Useful Make targets (paste into your Makefile)
Doctor / dashboard / cache warm-up
mk-doctor:
@echo "== Minikube doctor =="
minikube -p $(PROFILE) status || true
kubectl get nodes
kubectl -n $(NAMESPACE) get deploy,svc || true
mk-dashboard:
@echo "== Minikube dashboard (opens browser) =="
minikube -p $(PROFILE) dashboard --url &
warm-cache:
@echo "== Pre-pulling common tool images =="
docker pull returntocorp/semgrep:latest
docker pull zricethezav/gitleaks:latest
docker pull bridgecrew/checkov:latest
docker pull hadolint/hadolint:latest
docker pull aquasec/trivy:latest
docker pull anchore/syft:latest
docker pull zaproxy/zap-stable:latest
Then run:
make warm-cacheandmake mk-doctor.
7) Troubleshooting quick hits
A) Can’t get a service URL (make k8s-port prints nothing)
Ensure the app was deployed:
kubectl -n ship get deploy app kubectl -n ship get svc appRe-apply:
make k8s-apply && make k8s-verify make k8s-port
B) ZAP image not found / old name
- We use
zaproxy/zap-stable:latest. If you still haveowasp/zap2docker-stable, update the Makefile (already fixed in the starter).
C) Syft CLI changed flags
Use the new syntax in the Makefile:
docker run --rm -v $(PWD):/work anchore/syft:latest \ scan dir:/work/app -o json > artifacts/sbom.json
D) Very slow image loads
- Confirm Docker driver:
minikube start -p ship --driver=docker - We use
minikube image load(no registry needed). Avoiddocker system pruneduring the course.
E) Windows path/volume issues
- Run the repo from your user directory (e.g.,
C:\Users\<you>\...) so Docker Desktop can mount volumes. - Use Git Bash or PowerShell. If you see permission or mount errors, restart Docker Desktop and Minikube.
F) DNS/network hiccups inside Minikube
Restart Docker Desktop, then:
minikube -p ship stop && minikube -p ship startAs a last resort:
make mk-delete && make mk-up
G) Apple Silicon (M1/M2/M3)
- All images referenced are multi-arch. Docker Desktop handles
arm64transparently.
8) Nice-to-have Minikube add-ons (optional)
Dashboard (already wrapped by
make mk-dashboard)Ingress if you want nicer hostnames locally:
minikube -p ship addons enable ingress(Not required for this starter;
minikube service ... --urlis simpler and cross-platform.)
9) Clean up
make mk-down # stop cluster
make mk-delete # delete the 'ship' profile entirely
How to Install VSCODE (Mac • Windows • Linux)
macOS
Homebrew (recommended)
brew install --cask visual-studio-code
code --version
If code isn’t on PATH: open VS Code → Command Palette → “Shell Command: Install ‘code’ command in PATH”.
Windows
Winget (recommended)
winget install -e --id Microsoft.VisualStudioCode
code --version
Chocolatey
choco install vscode -y
code --version
Linux
Debian/Ubuntu (Microsoft repo)
sudo apt update
sudo apt install -y wget gpg
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /usr/share/keyrings/ms_vscode.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ms_vscode.gpg] https://packages.microsoft.com/repos/code stable main" | sudo tee /etc/apt/sources.list.d/vscode.list
sudo apt update
sudo apt install -y code
code --version
Fedora
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo sh -c 'echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=chapter-1\ngpgcheck=chapter-1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo'
sudo dnf check-update
sudo dnf install -y code
code --version
Arch (AUR)
# Use your AUR helper
paru -S visual-studio-code-bin
code --version
Recommended VS Code extensions (one-liners)
Install from a terminal:
code --install-extension golang.Go
code --install-extension ms-azuretools.vscode-docker
code --install-extension redhat.vscode-yaml
code --install-extension ms-kubernetes-tools.vscode-kubernetes-tools
code --install-extension ms-vscode.makefile-tools
code --install-extension eamodio.gitlens
code --install-extension yzhang.markdown-all-in-one
Why these
golang.Go: language server (gopls), debugging, tests, linters- Docker/YAML/Kubernetes: editing manifests & seeing cluster state
- Makefile Tools: task runner UI for your Make targets
- GitLens: easy code ownership & history
- Markdown All in One: better docs authoring
Go extension first-run
- On first open of a Go file, VS Code prompts to install tools. Accept it.
- Or install manually in a Go-enabled shell:
go install golang.org/x/tools/gopls@latest
go install github.com/go-delve/delve/cmd/dlv@latest
Optional nice-to-haves
code --install-extension streetsidesoftware.code-spell-checker
code --install-extension esbenp.prettier-vscode
Quick VS Code sanity check
- Open the starter repo folder in VS Code.
- Open
app/main.go→ you should see “Go” in the status bar (gopls active). - Press Run > Start Debugging (or use the Run icon) to debug the app locally.
- Try Terminal > Run Task… and pick a Make target (e.g.,
ci).
Chapter 1. Checklist
Use this to align your local-first setup with how your org builds and ships software.
Platform & Architecture
- Delivery model: What do you ship today? (SPA / REST / GraphQL / gRPC / monolith / microservices)
- Hosting posture: Cloud (which?) / on-prem / air-gapped / hybrid — any internet egress limits for scanners?
- Runtime target: Containers on K8s? Which cluster(s) will mirror your Minikube layout (namespaces, ingress)?
- Language mix: Mostly Go, or polyglot? Which languages need SAST/Secrets coverage day 1?
Source Control & CI
- Repo strategy: Monorepo vs multi-repo — where will the Make targets live (root or per-service)?
- CI system: What hosted CI will call
makelater (Actions / GitLab / Jenkins / other)? Who owns the runner images? - Branch gates: Do you already enforce PR checks (required status, code owners, signed commits)?
Data & Compliance
- Data classes: What data does the app touch? (PII/PHI/PCI) Any regulatory constraints that affect tooling choice?
- License stance: Any license allow/deny policy already? (e.g., GPL, AGPL)
- Audit evidence: Who needs artifacts (JSON/SBOM/reports), and in what format/cadence?
Local Constraints
- Developer machines: Are Docker/Minikube feasible for everyone (CPU/RAM policy, Windows/macOS/Linux mix)?
- Proxy/mirrors: Do dev networks require HTTP(S) proxies or internal package mirrors for builds?