Lab 3 — A new project, docs beside the code
One repository, docs and product as siblings, and a pipeline that publishes on every push.
You have: an idea for a codebase. You will have: a repository where
docs/ and product/ sit side by side, and a pipeline that turns the docs into
a container image on every push.
Needs: zel, a GitHub repository. A container runtime only if you want to
build locally as well.
The arrangement matters more than the commands. Documentation in the same repository as the code it describes stays in the same branch, the same review and the same pull request as the change it documents — which is the only arrangement under which it stays accurate.
Lay out the repository
zel new project acme --product-lang rust✓ acme docs/ the documentation, as Zellij content product/ yours .github/workflows/docs.yml validates on every pull request, image on main 12 files Write: zel dev acme/docs Code: cd acme/product && cargo initThe
product/folder is deliberately empty.--product-langprints the command that starts a project in your language — with your project's name in it — and writes nothing:cargo initis better than anything Zellij would put there, and it is what you were going to run anyway.cd acme/product && cargo initWrite something, and look at it
cd acme zel dev docs --openEdit anything under
docs/and the browser follows. This is Lab 1 from step 2 — the loop is the same wherever the folder lives.Push it
git init && git add . && git commit -m "acme" gh repo create acme --private --source=. --pushAdd the pipeline
zel ci init github --content docs --target imageIt writes this, and prints what you still have to do that a file cannot:
name: Documentation on: push: branches: [main] paths: ['docs/**'] permissions: contents: read packages: write jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Install zel run: | curl -fsSL https://raw.githubusercontent.com/mylife-inc/releases/main/zellij/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - run: zel check docs - run: zel image build docs --tag ghcr.io/${{ github.repository }}/docs:${{ github.sha }} - run: zel image push ghcr.io/${{ github.repository }}/docs:${{ github.sha }}Four steps. Worth noticing:
paths: ['docs/**']— a push that only touchesproduct/does nothing.zel checkbefore the build. A broken link fails in a second rather than after a four-minute compile, and never reaches an image.- No Dockerfile.
zel image buildwrites the multi-stage build itself, and it stays correct when the base image changes. GITHUB_TOKENis the only credential, and GitHub issues it to the job.packages: writeis what lets it push to GHCR.
Turn on package writing
The one setting that is not in a file. Repository → Settings → Actions → General → Workflow permissions → Read and write permissions.
Without it the push step fails with a 403 that reads like an authentication problem and is a permissions problem.
Watch it run
git commit --allow-empty -m "trigger" && git push gh run watchWhen it finishes, the image is at
ghcr.io/you/acme/docs:<sha>. Pull and run it anywhere:docker run -p 3000:3000 ghcr.io/you/acme/docs:<sha>Also check on pull requests
The most valuable pipeline is the one that fails a pull request whose documentation is broken — and it needs no credentials at all.
on: pull_request: paths: ['docs/**'] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Install zel run: | curl -fsSL https://raw.githubusercontent.com/mylife-inc/releases/main/zellij/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - run: zel check docsA link to a page somebody deleted, a section type that does not exist, a sidebar entry pointing at a missing file — caught before review rather than after deploy.
Publishing a site rather than an image
An image is right when the docs deploy alongside your services. If they should just be a website, swap the pipeline:
zel ci init github --deploy netlify - run: zel check docs
- run: zel deploy netlify docs
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}Getting those two values, and pointing a custom domain at the result, is Lab 1, step 6.
Settling the repeated arguments
docs on every command gets old. Put it in a file once:
content = "docs"
[build]
target = "image"
[check]
strict = trueNow it is zel check, zel dev, zel build — in the pipeline too. See
zel.toml.
