This repo contains the source code and content for my website at wincent.dev.
Content (stored on the content branch) is authored in plain-text-friendly markup formats like Markdown, written to static HTML output files on the public branch, and a small Rust application (built from the main branch) is used to handle dynamic parts of the site, such as search.
Note
While Masochist is mostly a static site generator, in its original form it was a dynamic stack built using React, Relay, and GraphQL. If you're interested in that, take a look at the main branch as it used to exist at commit b06dbb8488f88b3d.
- Caddy: Web server.
- Docker: Containers and orchestration.
- Git: Content storage.
- Lightning CSS: CSS minification.
- Rust: Build and backend language.
- Rocket: Backend web framework.
- SWC: JavaScript minification.
- dprint: Code and configuration formatting.
Supporting tools and technologies:
- Markdown: Preferred content markup.
- Neovim, with help from Corpus: Content editing.
- Marked 2: Local content previewing.
- Rust toolchain (install via rustup or with
brew install rustor similar). - Git (used during build and deployment; the system installed Git1 may be adequate, but I use Homebrew's Git, via
brew install git). - Colima (
brew install colima). - Docker (
brew install docker docker-buildx docker-compose). - dprint (
brew install dprint).
The repo uses three branches, each with its own role:
main: Rust source code (this branch).content: Source content (Markdown, images, static files).public: Static build output (to be served by Caddy).
Set up worktrees for the other two branches:
bin/configure-worktrees
There are 3 remotes, configured with bin/configure-remotes:
origin: Canonical repo at git.wincent.dev.github: Main mirror at github.com.masochist: An Amazon EC2 instance where the actual site runs.
bin/build: Builds the static artifacts.bin/check-format: Check formatting.bin/check-links: Check for broken internal links.bin/clippy: Run Clippy linter.bin/configure-remotes: Configuresorigin,github,masochistandallremotes.bin/configure-worktrees: Configurescontentandpublicworktrees.bin/deploy: Deploys to EC2.bin/dev: Runs Docker Compose locally.bin/ecr: Builds and uploads container images to Amazon ECR (Elastic Container Registry)..bin/format: Fix formatting.bin/prod: Runs Docker Compose on remote host.bin/pull: Fetches all remotes and updatesmain,content, andpubliccheckouts.bin/push: Pushes all branches (main,content,public) to all remotes (origin,github,masochist).
Install Docker and Colima (CLI alternative to docker-desktop cask):
brew install docker docker-buildx docker-compose colima
# Start Colima.
# --vm-type=vz: uses Apple's Virtualization Framework (faster on Apple silicon).
# --vz-rosetta: allows running x86_64 (Intel) container images; not needed but a useful default.
colima start --cpu 4 --memory 16 --vm-type=vz --vz-rosetta
# Confirm Colima is working.
docker ps
docker context ls # Should show Colima is current context (indicated with "*").
docker info
docker compose version
colima status
# Configure Colima to start at boot.
brew services start colima
# (Optional) For tools that expect standard docker.sock path, provide symlink as a convenience:
mkdir -p ~/.docker/run
ln -sf ~/.colima/default/docker.sock ~/.docker/run/docker.sock
Build the static site, then start the stack:
bin/build
bin/dev
This starts Caddy on https://localhost:2443 (self-signed cert) with the Rocket search server proxied behind it.
For isolated development using Tart (to more safely run AI coding agents), VM management is handled by sb, a general-purpose sandbox tool installed via the wincent/wincent dotfiles repo. Project-specific configuration lives in .sandboxrc at the repo root.
brew install sshpass cirruslabs/cli/tart
# Generate a long-lived OAuth token for Claude Code (valid 1 year):
claude setup-token
export CLAUDE_CODE_OAUTH_TOKEN=<token> # Add to shell profile.
# Alternatively if using Pi (or similar) use an API key:
export ANTHROPIC_API_KEY=<key> # Add to shell profile.
sb create # One-time: clones base image, provisions Docker + Rust, injects branches.
sb status # Check whether the VM is running and its IP.
Other lifecycle commands include: sb destroy, sb reset, sb restart, sb start, sb stop, and sb pull.
sb ssh code/masochist/bin/dev
On the host, visit https://localhost:3443 (accept the self-signed cert). Port 3080 reaches the Rocket backend directly, bypassing Caddy.
If the service fails to start with:
runc run failed: unable to start container process: error during container init: unable to apply apparmor profile
the cause may be a corrupt /etc/apparmor.d/tunables/home.d/ubuntu file2; you can regenerate it and have Docker pick it up by running the following in the VM:
sudo dpkg-reconfigure apparmor
sudo systemctl restart docker
Push host branches into the VM:
sb inject # Push all branches (main, content, public).
sb inject --force # Force-push (discard VM-only commits).
Pull VM changes back to the host:
sb extract # Show VM-only commits per branch.
sb apply # Rebase and GPG-sign VM commits onto host branches.
After applying, verify and push:
cargo check && bin/test && bin/check-format && bin/clippy
bin/push # Push to production remotes (from host).
Run sb help for the full command list.
I set up the instance using Ansible, but the equivalent manual steps are as follows:
sudo dnf install docker -y
sudo systemctl enable docker
sudo systemctl start docker
# Add ec2-user so we can easily run Docker commands.
sudo usermod -aG docker ec2-user
# Add masochist user because our `bin/prod script runs as masochist,
# and needs to talk to Docker.
sudo usermod -aG masochist ec2-user
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
The instance needs an IAM role with the AmazonEC2ContainerRegistryReadOnly policy attached.
Install the credential helper so Docker can authenticate with ECR automatically:
sudo dnf install amazon-ecr-credential-helper -y
Then configure Docker to use it:
mkdir -p ~/.docker
cat > ~/.docker/config.json << 'EOF'
{
"credsStore": "ecr-login"
}
EOF
Create the repository (one-time).
As noted in the previously, you need Colima installed in order to complete the build.
bin/build
aws login
aws ecr describe-registry --region us-east-1 --query registryId --output text
export ECR_ACCOUNT_ID=...
Push all branches, build and upload the Docker image, then deploy:
bin/push
bin/ecr build
bin/ecr upload
bin/deploy
Each step is independent and can be retried on failure.
masochist-lib/: Shared library (content parsing, git interaction, indexing).masochist-build/: Static site generator (depends on comrak for Markdown).masochist-server/: Rocket search server (no Markdown rendering at runtime).ops/: Caddyfile, Dockerfiles, Docker Compose configs.bin/: Build, dev, and deploy scripts.public/(worktree): Public branch (build output, served by Caddy).content/(worktree): Content branch (Markdown, images, static files).
See the introductory blog post, "Introducing Masochist".