|
| 1 | +--- |
| 2 | +title: "How We Do Documentation Engineering" |
| 3 | +meta_title: "How We Do Documentation Engineering" |
| 4 | +description: "" |
| 5 | +date: 2024-12-16T08:00:00Z |
| 6 | +image: "/images/posts/2024/documentation-engineering/how-we-do-product-documentation-engineering.jpg" |
| 7 | +categories: ["Product", "Documentation", "Engineering"] |
| 8 | +author: "Medcl" |
| 9 | +tags: ["Product", "Documentation", "Engineering"] |
| 10 | +draft: false |
| 11 | +--- |
| 12 | + |
| 13 | +At INFINI Labs, we see product documentation as an integral part of the product development process. Effective documentation ensures that users understand, adopt, and get the most out of our offerings. |
| 14 | + |
| 15 | +Take a look at our documentation site: [https://docs.infinilabs.com/](https://docs.infinilabs.com/). It hosts detailed documentation for each of our products. |
| 16 | + |
| 17 | +Managing comprehensive documentation might seem like an enormous task, especially since we don’t have a dedicated documentation team. However, with limited resources and many competing priorities, we’ve streamlined a practical and efficient approach to product documentation engineering. |
| 18 | + |
| 19 | +So, how do we do it? |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## The Tools We Use |
| 24 | + |
| 25 | +Our documentation workflow relies on the following tools: |
| 26 | + |
| 27 | +- **[GitHub Pages](https://pages.github.com/)**: For hosting our documentation website. |
| 28 | +- **[GitHub Actions](https://github.com/features/actions)**: To automate builds and deployments. |
| 29 | +- **[Hugo](https://gohugo.io/)**: A fast and flexible static site generator. |
| 30 | +- **[Markdown](https://www.markdownguide.org/)**: To write clean and easily maintainable documentation. |
| 31 | + |
| 32 | +We love GitHub's ecosystem for its reliability and developer-friendly features. Using GitHub Pages, we can host our documentation effortlessly. Once our documentation is compiled into static files using Hugo, it’s lightweight, fast, and easy to serve to users. |
| 33 | + |
| 34 | +To enhance the user experience further, we integrate **offline search functionality** powered by our own [pizza-searchbox](https://github.com/infinilabs/pizza-searchbox/). This ensures users can quickly find what they need, even without an internet connection. |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +## How It All Works Together |
| 39 | + |
| 40 | +### Organizing Components Across Repositories |
| 41 | + |
| 42 | +We maintain separate repositories for each part of the documentation workflow. The final compiled version of all product documentation lives here: |
| 43 | + |
| 44 | +- Compiled docs: [https://github.com/infinilabs/docs](https://github.com/infinilabs/docs) |
| 45 | +- Hugo theme for docs: [https://github.com/infinilabs/docs-theme](https://github.com/infinilabs/docs-theme) |
| 46 | +- Product with docs: [https://github.com/infinilabs/gateway](https://github.com/infinilabs/gateway) |
| 47 | + |
| 48 | +By separating concerns, we make it easier to manage updates, streamline collaboration, and keep everything organized. |
| 49 | + |
| 50 | + |
| 51 | +As you can see the layout of compiled folder is looks like this: |
| 52 | + |
| 53 | + |
| 54 | + |
| 55 | +Each products have folder for each different version, and the `main` is alwasy point to the latest version. |
| 56 | + |
| 57 | +### And how did these static docs coming from? |
| 58 | + |
| 59 | +Checkout this specify [product's](https://github.com/infinilabs/gateway/tree/main/docs) repo for example: |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | +As you can see, in the product’s repository, there’s a folder named `docs`, which contains all the documentation specific to that product. |
| 64 | + |
| 65 | +Alongside it, there’s a `config.yaml` defines the basic configuration for the product: |
| 66 | + |
| 67 | +```yaml |
| 68 | +# VERSIONS=latest,v1.0 hugo --minify --baseURL="/product/v1.0/" -d public/product/v1.0 |
| 69 | + |
| 70 | +title: INFINI Gateway |
| 71 | +theme: book |
| 72 | + |
| 73 | +# Book configuration |
| 74 | +disablePathToLower: true |
| 75 | +enableGitInfo: false |
| 76 | + |
| 77 | +# Needed for mermaid/katex shortcodes |
| 78 | +markup: |
| 79 | + goldmark: |
| 80 | + renderer: |
| 81 | + unsafe: true |
| 82 | + tableOfContents: |
| 83 | + startLevel: 1 |
| 84 | + |
| 85 | +# Multi-lingual mode config |
| 86 | +# There are different options to translate files |
| 87 | +# See https://gohugo.io/content-management/multilingual/#translation-by-filename |
| 88 | +# And https://gohugo.io/content-management/multilingual/#translation-by-content-directory |
| 89 | +defaultContentLanguage: en |
| 90 | +languages: |
| 91 | + en: |
| 92 | + languageName: English |
| 93 | + contentDir: content.en |
| 94 | + weight: 3 |
| 95 | + |
| 96 | + |
| 97 | +menu: |
| 98 | + before: [] |
| 99 | + after: |
| 100 | + - name: "Github" |
| 101 | + url: "https://github.com/infinilabs/gateway" |
| 102 | + weight: 10 |
| 103 | + |
| 104 | +... |
| 105 | +EMITTED |
| 106 | +... |
| 107 | +``` |
| 108 | + |
| 109 | +Make sure you changed the right github's repo address and the product name. |
| 110 | + |
| 111 | +And also there’s a `Makefile` that defines how we build the docs: |
| 112 | + |
| 113 | +```shell |
| 114 | +SHELL=/bin/bash |
| 115 | + |
| 116 | +# Basic info |
| 117 | +PRODUCT?= $(shell basename "$(shell cd .. && pwd)") |
| 118 | +BRANCH?= main |
| 119 | +VERSION?= $(shell [[ "$(BRANCH)" == "main" ]] && echo "main" || echo "$(BRANCH)") |
| 120 | +CURRENT_VERSION?= $(VERSION) |
| 121 | +VERSIONS?= "main" |
| 122 | +OUTPUT?= "/tmp/docs" |
| 123 | +THEME_FOLDER?= "themes/book" |
| 124 | +THEME_REPO?= "https://github.com/infinilabs/docs-theme.git" |
| 125 | +THEME_BRANCH?= "main" |
| 126 | + |
| 127 | +.PHONY: docs-build |
| 128 | + |
| 129 | +default: docs-build |
| 130 | + |
| 131 | +docs-init: |
| 132 | + @if [ ! -d $(THEME_FOLDER) ]; then echo "theme does not exist";(git clone -b $(THEME_BRANCH) $(THEME_REPO) $(THEME_FOLDER) ) fi |
| 133 | + |
| 134 | +docs-env: |
| 135 | + @echo "Debugging Variables:" |
| 136 | + @echo "PRODUCT: $(PRODUCT)" |
| 137 | + @echo "BRANCH: $(BRANCH)" |
| 138 | + @echo "VERSION: $(VERSION)" |
| 139 | + @echo "CURRENT_VERSION: $(CURRENT_VERSION)" |
| 140 | + @echo "VERSIONS: $(VERSIONS)" |
| 141 | + @echo "OUTPUT: $(OUTPUT)" |
| 142 | + |
| 143 | +docs-config: docs-init |
| 144 | + cp config.yaml config.bak |
| 145 | + # Detect OS and apply the appropriate sed command |
| 146 | + @if [ "$$(uname)" = "Darwin" ]; then \ |
| 147 | + echo "Running on macOS"; \ |
| 148 | + sed -i '' "s/BRANCH/$(VERSION)/g" config.yaml; \ |
| 149 | + else \ |
| 150 | + echo "Running on Linux"; \ |
| 151 | + sed -i 's/BRANCH/$(VERSION)/g' config.yaml; \ |
| 152 | + fi |
| 153 | + |
| 154 | +docs-build: docs-config |
| 155 | + hugo --minify --theme book --destination="$(OUTPUT)/$(PRODUCT)/$(VERSION)" \ |
| 156 | + --baseURL="/$(PRODUCT)/$(VERSION)" |
| 157 | + @$(MAKE) docs-restore-generated-file |
| 158 | + |
| 159 | +docs-place-redirect: |
| 160 | + echo "<!DOCTYPE html> <html> <head> <meta http-equiv=refresh content=0;url=main /> </head> <body> <p><a href=main />REDIRECT TO THE LATEST_VERSION</a>.</p> </body> </html>" > $(OUTPUT)/$(PRODUCT)/index.html |
| 161 | + |
| 162 | +docs-restore-generated-file: |
| 163 | + mv config.bak config.yaml |
| 164 | + |
| 165 | +``` |
| 166 | + |
| 167 | + |
| 168 | +Usually, there’s no need to make any changes—simply copy these files to your new product, and everything will work seamlessly. |
| 169 | + |
| 170 | +--- |
| 171 | + |
| 172 | +## Automation Makes Everything Easier |
| 173 | + |
| 174 | +And we use `.github/workflows/build-docs.yml` to define how to build the documentation once someone pushed the code, or someone released a new version, take a look at the github actions: |
| 175 | + |
| 176 | +```yaml |
| 177 | +name: Build and Deploy Docs |
| 178 | + |
| 179 | +on: |
| 180 | + push: |
| 181 | + branches: |
| 182 | + - main |
| 183 | + - 'v*' |
| 184 | + tags: |
| 185 | + - 'v*' |
| 186 | + |
| 187 | +jobs: |
| 188 | + build-deploy-docs: |
| 189 | + runs-on: ubuntu-latest |
| 190 | + |
| 191 | + steps: |
| 192 | + - name: Checkout Product Repo |
| 193 | + uses: actions/checkout@v2 |
| 194 | + with: |
| 195 | + fetch-depth: 0 |
| 196 | + |
| 197 | + - name: Set Variables Based on Ref |
| 198 | + id: vars |
| 199 | + run: | |
| 200 | + PRODUCT_NAME=$(basename $(pwd)) # Get the directory name as the product name |
| 201 | + echo "PRODUCT_NAME=$PRODUCT_NAME" >> $GITHUB_ENV |
| 202 | + CURRENT_REF=${GITHUB_REF##*/} |
| 203 | + IS_SEMVER=false |
| 204 | + SEMVER_REGEX="^v([0-9]+)\.([0-9]+)\.([0-9]+)$" |
| 205 | +
|
| 206 | + if [[ "${GITHUB_REF_TYPE}" == "branch" ]]; then |
| 207 | + if [[ "$CURRENT_REF" == "main" ]]; then |
| 208 | + echo "VERSION=main" >> $GITHUB_ENV |
| 209 | + echo "BRANCH=main" >> $GITHUB_ENV |
| 210 | + elif [[ "$CURRENT_REF" =~ $SEMVER_REGEX ]]; then |
| 211 | + IS_SEMVER=true |
| 212 | + echo "VERSION=$CURRENT_REF" >> $GITHUB_ENV |
| 213 | + echo "BRANCH=$CURRENT_REF" >> $GITHUB_ENV |
| 214 | + else |
| 215 | + echo "Branch '$CURRENT_REF' is not a valid semantic version. Skipping build." |
| 216 | + exit 0 |
| 217 | + fi |
| 218 | + elif [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then |
| 219 | + if [[ "$CURRENT_REF" =~ $SEMVER_REGEX ]]; then |
| 220 | + IS_SEMVER=true |
| 221 | + echo "VERSION=$CURRENT_REF" >> $GITHUB_ENV |
| 222 | + echo "BRANCH=main" >> $GITHUB_ENV # Set BRANCH to 'main' for tags |
| 223 | + else |
| 224 | + echo "Tag '$CURRENT_REF' is not a valid semantic version. Skipping build." |
| 225 | + exit 0 |
| 226 | + fi |
| 227 | + fi |
| 228 | +
|
| 229 | + # Gather branches and tags, filter for semantic versions, sort, remove duplicates |
| 230 | + VERSIONS=$(git for-each-ref refs/remotes/origin refs/tags --format="%(refname:short)" | \ |
| 231 | + grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | sort -Vr | uniq | tr '\n' ',' | sed 's/,$//') |
| 232 | + echo "VERSIONS=main,$VERSIONS" >> $GITHUB_ENV |
| 233 | +
|
| 234 | + - name: Install Hugo |
| 235 | + run: | |
| 236 | + wget https://github.com/gohugoio/hugo/releases/download/v0.79.1/hugo_extended_0.79.1_Linux-64bit.tar.gz |
| 237 | + tar -xzvf hugo_extended_0.79.1_Linux-64bit.tar.gz |
| 238 | + sudo mv hugo /usr/local/bin/ |
| 239 | +
|
| 240 | + - name: Checkout Docs Repo |
| 241 | + uses: actions/checkout@v2 |
| 242 | + with: |
| 243 | + repository: infinilabs/docs |
| 244 | + path: docs-output |
| 245 | + token: ${{ secrets.DOCS_DEPLOYMENT_TOKEN }} |
| 246 | + |
| 247 | + - name: Build Documentation |
| 248 | + run: | |
| 249 | + (cd docs && OUTPUT=$(pwd)/../docs-output make docs-build docs-place-redirect) |
| 250 | +
|
| 251 | + - name: Commit and Push Changes to Docs Repo |
| 252 | + working-directory: docs-output |
| 253 | + run: | |
| 254 | + git config user.name "GitHub Actions" |
| 255 | + git config user.email "[email protected]" |
| 256 | + |
| 257 | + if [[ -n $(git status --porcelain) ]]; then |
| 258 | + git add . |
| 259 | + git commit -m "Rebuild $PRODUCT_NAME docs for version $VERSION" |
| 260 | + git push origin main |
| 261 | + else |
| 262 | + echo "No changes to commit." |
| 263 | + fi |
| 264 | +
|
| 265 | + - name: Rebuild Docs for Latest Version (main), if not already on main |
| 266 | + run: | |
| 267 | + # Only rebuild the main branch docs if the current ref is not "main" |
| 268 | + if [[ "$CURRENT_REF" != "main" ]]; then |
| 269 | + echo "Switching to main branch and rebuilding docs for 'latest'" |
| 270 | +
|
| 271 | + # Checkout the main branch of the product repo to rebuild docs for "latest" |
| 272 | + git checkout main |
| 273 | +
|
| 274 | + # Ensure the latest changes are pulled |
| 275 | + git pull origin main |
| 276 | + |
| 277 | + # Build Docs for Main Branch (latest) |
| 278 | + (cd docs && OUTPUT=$(pwd)/../docs-output VERSION="main" BRANCH="main" make docs-build docs-place-redirect) |
| 279 | +
|
| 280 | + # Commit and Push Latest Docs to Main |
| 281 | + cd docs-output |
| 282 | + git config user.name "GitHub Actions" |
| 283 | + git config user.email "[email protected]" |
| 284 | + |
| 285 | + if [[ -n $(git status --porcelain) ]]; then |
| 286 | + git add . |
| 287 | + git commit -m "Rebuild $PRODUCT_NAME docs for main branch with latest version" |
| 288 | + git push origin main |
| 289 | + else |
| 290 | + echo "No changes to commit for main." |
| 291 | + fi |
| 292 | + else |
| 293 | + echo "Current ref is 'main', skipping rebuild for 'latest'." |
| 294 | + fi |
| 295 | + working-directory: ./ # Working in the product repo |
| 296 | +``` |
| 297 | +
|
| 298 | +If you look closely at the GitHub Actions workflow, it simplifies the entire process by automating key tasks: |
| 299 | +- Monitor Branches and Tags: Watches for branches or tags starting with v and validates them as semantic versioned branches or tags. Only valid versions trigger the documentation build process. |
| 300 | +- Fetch Theme: Pulls the documentation theme from a separate repository to ensure a consistent look and feel across all products. |
| 301 | +- Build and Deploy: Compiles the documentation into static files using Hugo, commits the changes with a clear and informative message, and pushes the updates to the docs repository. |
| 302 | +
|
| 303 | +Through GitHub Actions, the entire workflow becomes seamless: |
| 304 | +- Compile: Converts Markdown files into a polished static site using Hugo. |
| 305 | +- Deploy: Publishes the site effortlessly to GitHub Pages. |
| 306 | +
|
| 307 | +This automation reduces manual effort, ensures consistency across documentation, and allows us to focus on delivering quality content to users. |
| 308 | +
|
| 309 | +--- |
| 310 | +
|
| 311 | +## Why This Approach Works for Us |
| 312 | +
|
| 313 | +1. **Efficiency**: With automation, we can focus on content rather than operations. |
| 314 | +2. **Scalability**: As our product offerings grow, this workflow scales effortlessly. |
| 315 | +3. **User Experience**: A fast, searchable, and offline-ready documentation site means better support for our users. |
| 316 | +
|
| 317 | +--- |
| 318 | +
|
| 319 | +## Final Thoughts |
| 320 | +
|
| 321 | +Product documentation isn’t just a necessity—it’s a competitive advantage. By leveraging the right tools and automating where possible, we’ve built a process that delivers high-quality documentation without needing a dedicated team. |
| 322 | +
|
| 323 | +Want to see it in action? Visit our documentation site here: [https://docs.infinilabs.com/](https://docs.infinilabs.com/). |
| 324 | +
|
| 325 | +Let us know what you think! Your feedback helps us improve not only our products but also the way we document and share them with the world. |
0 commit comments