Skip to content

DocOps/box

Repository files navigation

DocOps Box

Get up and running with docs-as-code!

DocOps Box is a documentation multi-tool for your Mac or PC.

This project includes an introductory guide and quick-start procedure for establishing and maintaining a broadly capable environment for documentation management and document-processing work.

DocOps Box uses Docker and VS Code Dev Containers to provide a consistent, reproducible environment for digital document management across different operating systems and team members.

The provided system also works for solo practitioners who want a stable, isolated environment for their docs projects without the hassle of managing dependencies on their host system.

💡

LLMs or people engaging LLMs/agents to help them use DocOps Box should reference the Agent User Guidance skill.

Even if you don’t use the images or tools provided, this guide should still be helpful for anyone orienting to the world of docs-as-code tools and workflows. It includes a section for “host installation” of all the tools supported by the container approach, so you can choose your own adventure if you prefer to set up your workstation without Docker.

Introduction

This project is geared as a guide and assets for meeting non-developer tech or clerical workers, typically on their Windows or maybe MacOS systems, not yet set up as “development environments” with tools like Ruby, Node.js, Python, Git, Pandoc, Vale and the world of capabilities that these tools open up.

📎
This entire project is geared toward Windows, Mac, and Linux users, but it assumes virtually zero Linux/Unix knowledge or preference. You will be using a command line (terminal), and it needs to be (1) Mac-based or (2) Linux-based (including via WSL2 on Windows).

This project is intended to meet users where I have found these “tech-savvy non-programmers” among my clients the past several years, such as technical writers, project managers, and professional document wranglers like paralegals, non-software engineers, researchers, and educators.

This toolkit is intended to provide a proper swath of technologies in the form of a specially designed Docker image🔖 you can run as a container🔖 on your system, whatever that system may be.

DocOps Box is specifically geared toward the AYL DocStack: applications using AsciiDoc, YAML, and Liquid to build documents and documentation. However, the max images can handle a huge swath of the most common tools and formats used in the docs-as-code world, such as JavaScript, Markdown, reStructuredText, JSON, XML, and more.

📎
DocOps Box is tested on Docusaurus, Antora, Astro, Jekyll, MkDocs, Sphinx, and 11ty. The max images are suitable for projects running any of these platforms as well as all the pre-installed tools found in Tooling Overview.

The main intent is to provide the basics for these fairly technical/non-expert users to operate with the power of advanced “docs-as-code” techniques that only programmers, hackers, and IT professionals have typically bothered messing with until recently. See Who is This Software For? for more on the intended user base and use cases for this project.

If you know you want to get started with DocOps Box, skip to Prerequisites and perform any necessary installations there.

The next few sections are for users who want to understand the rationale and context for this project before diving in.

Tooling Overview

The only real prerequisite for the “Dockerized” (“containerized”) approach to establishing a robust DocOps environment, as provided in this project, is that you have Docker working on top of a Unix-like shell🔖.

It makes the most sense for users who need a consistent mix of tools across multiple projects; setting up a separate Docker image per tool is at least as complicated as a native install.

📎
Windows users will need to set up a WSL2 kernel and a Linux distribution if they have not already done so. This much is true of any Windows-based approach advised by this project.

Technologies included in the DocOps Box Docker images are:

Zsh

The powerful and elegant terminal shell environment that is more user-friendly than Bash, already configured with OhMyZsh. work images only; live images use Bash

Git

The most popular version control system, which is used to track changes to your codebase and to share it with others

Ruby

A runtime environment🔖 for executing Ruby command-line utilities and managing Ruby dependencies

Node.js

A runtime environment for numerous auxiliary tools that have no Ruby equivalent; included in max images only

Python

A runtime environment for the inevitable Python utilities; included in max images only

Pandoc

An extraordinary document migration utility that can convert between a huge range of formats

Vale

A document validation utility (linter) that can check markup-formatted writing for consistent style and preferred grammar

OpenAPI tools

Three excellent OpenAPI Specification🔖 utilities, for validating/linting OAS documents and generating API reference documentation from them (Redocly CLI, Vacuum, and Speakeasy CLI)

text editors

The images provide the GNU nano (default) and Vim, TUI🔖 utilities for quick edits in the interactive shell.

LibreOffice

Powerful CLI utilities behind the popular office suite; included automatically in max images

Ruby tools

Out of the box (so to speak): Asciidoctor (.adoc.html/.pdf/.epub), Kramdown-AsciiDoc (.md.adoc), Nokogiri (HTML/XML parsing), and other handy Ruby gems. Add your own per project using Gemfile; see Adding Runtime Dependencies.

See the project’s Dockerfile for the full list of installed packages and utilities. Many are secondarily documented below in Auxiliary Tools as well as Installing Everything to Host.

This environment is suitable for users to execute scripts and runtime applications like the ones I provide for and use with my clients. It’s what you need to apply my or anyone else’s Ruby-based documentation toolchain or tech stack to your specific purposes.

This project should also suit if your toolchain is Javascript-based (Docusaurus, Antora, Next.js, SvelteKit, 11ty, Astro, etc) or Python-based (Sphinx/ReadTheDocs, MkDocs). However, if your toolchain excludes Ruby altogether, this Ruby-centric project might not be the most efficient route to a stable coding environment.

When to Use DocOps Box

This project is not intended to be a “one size fits all” solution for every docs-as-code project; it is optimized for relatively complex situations (multiple projects, multiple/complex toolchains, etc).

Use this table to decide if DocOps Box is right for your situation.

Scenario Recommendation

You occasionally use one or two DocOps Lab tools

Use each tool’s own Docker image directly

You regularly use multiple DocOps Lab tools

Use DocOps Box (min or max image)

Your toolchain uses Ruby and Node/Python tools

Use a DocOps Box max image

Your toolchain uses runtimes other than Ruby, Node, or Python

DocOps Box will be partially helpful or unsuitable; advanced users might consider extending the image

You are setting up a team documentation environment

Use DocOps Box with a shared .env file in Git

You need a CI/CD🔖 pipeline for docs automation

Use DocOps Box live image

Host Install vs DocOps Box

DocOps Box, as a project, is not about pushing you into using any specific software, including our Docker images or our dxbx script.

Full “native” or host installation instructions for all core DocOps Box-supported components are included in an appendix: Installing Everything to Host.

Criterion Host Install DocOps Box

Setup time (first use)

45-90 min

~5 min

Reproducibility across machines

High effort

Automatic via config files

Team-wide standardization

Requires active coordination, docs

Git-committed .env + docopsbox.yml

Works on Windows

WSL2 + manual setup

WSL2 + Docker

IDE integration (VS Code)

Full native

Full via Dev Containers extension

Per-project dependencies isolation

As configured per runtime/project

Named volumes 🔖 handled by dxbx

Suitable for CI/CD

Requires environment setup steps

Available via live image

Prerequisites

All of the software required to run DocOps Box is free, open source, and strongly recommended for any modern code-like documentation workflow. You cannot go wrong having VS Code and Docker on your workstation, even if you do not end up using DocOps Box over the long term.

💡
This document is intended to guide you to competency in the world of docs-as-code, not merely using dxbx and its Docker containers. Everything advised has a graceful fallback to direct installation on your host system.

Work through the following steps in order.

This section is somewhat long but should move along quickly. All of these apps are critical, some are likely already available on your system, and this guide covers installing them on any major platform.

Terminal App (All OSes; Advised)

There is no avoiding the command line in the docs-as-code world. If you already have a terminal you like, use it and skip ahead.

💡
It is strongly advised that you choose a terminal app other than the one in VS Code, simply for differentiation of duties, at least when getting started. Use VS Code’s terminal inside the containerized environment; use an external terminal for dxbx commands and other host-level work.

If you are unaware of or unhappy with your terminal app, here are my recommendations per operating system.

Warp and Wave

By far my two favorite terminal apps are Warp and Wave.

Both are freemium open-source models with generous free tiers.

Warp has a fully integrated terminal that can be a little bit overwhelming.

Wave is a chat-only tool for now, so you have to copy and paste commands and output back and forth, though the chat can automatically read local files.

Host Terminal Apps

If you prefer to install from an app store or package manager, here are some good options on all three platforms.

  • Windows users can use the built-in Windows Terminal, which supports multiple shells including PowerShell and WSL2. If it’s not installed, get it from the Microsoft Store: Windows Terminal.

  • MacOS users can use the built-in Terminal app, but most users prefer iTerm2, which is also available as a Homebrew package:

    brew install --cask iterm2
  • Linux users are unlikely to find better options than Wave or Warp. If you are a Linux user who does not already have a preference, try one of those or your distro’s default terminal app.

VS Code (All OSes; Advised)

The recommended daily workflow uses VS Code with the Dev Containers extension as your main interface.

📎
VS Code is not truly a requirement for the DocOps Box workflow, but it is strongly recommended for novice users without a strong reason to choose another workflow. Your favorite IDE/text editor is just as valid, in combination with its own terminal or another that you prefer. If you have no strong preference, give VS Code a try.
  1. Install Visual Studio Code.

    📎
    Linux/GNOME users:
    On first launch, GNOME may prompt “Choose password for new keyring”. Press Escape to dismiss it; no password is needed for local development.
  2. Install the Dev Containers extension from the VS Code Marketplace:

    ms-vscode-remote.remote-containers

    (Or search for “Dev Containers” in the Extensions panel.)

Whenever your workspace path in VS Code is configured with a .devcontainer/devcontainer.json, VS Code will prompt you to “Reopen in Container”, which gives you the full DocOps Box environment with all the tools installed and configured.

Or, you can always use dxbx vsc from the project root directory in your terminal to open a VS Code window with the containerized environment.

Recommended plugins:
Recommended for AsciiDoc/YAML/Liquid work:
Recommended configuration changes under File  Preferences  Settings:
  • Set Wrapping Indent to indent

  • Set Editor: Format On Save to true

Privacy/security settings:
  • Search telemetry and disable where you prefer:

    • Set Telemetry: Feedback to disabled

    • Set Telemetry: Telemetry Level to off

  • Set Extensions: Auto Update to true

💡

Bash 4+ (MacOS Only)

The quick-start procedure will ensure you have Bash 4 or higher on your host. For legal reasons, MacOS comes with Bash 3, but it is highly advised and 100% harmless to add Bash 4+ to your system.

📎
Adding Bash 4 does not replace Z Shell as your default system/interactive shell.

The bootstrap script will offer to install Bash 4+ via Homebrew (as well as Homebrew itself) if not already present.

Like all prerequisites, Bash 4 is an essential tool for working with developer tools and workflows.

💡
Mac users can skip to Docker (All OSes).

WSL2 (Windows Only)

💡
If you are on MacOS or Linux, skip this step.

The most reliable way to use a Unix-like shell on Windows is to install via the oddly named Windows Subsystem for Linux or WSL2. This is the biggest step for Windows users, but it is also straightforward and well-documented by Microsoft.

  1. Ensure your Windows version supports WSL2.

    Press Win+R, type winver, and press Enter. If your version is Windows 10 Build 19041 or higher, or any Windows 11, you can proceed. If not, use the manual process.

  2. Open Windows Terminal as an administrator.

    Right-click the Windows Terminal icon and select “Run as administrator”.

  3. Run the WSL2 installer:

    Standard Ubuntu WSL installation
    wsl --install

    This installs WSL2 and Ubuntu in one step.

    💡

    While Ubuntu is by far the most documented distribution commonly used with WSL, DocOps Lab recommends Fedora, which is a less bloated and less commercial distro.

    wsl --install -d FedoraLinux-43

That last command should perform the entire setup procedure.

📎
If this procedure does not work, follow the official Microsoft installation guide.

To enter a WSL2 session in the future, open your terminal client and enter wsl.

All project folders intended for use with DocOps Box should live inside the WSL2 filesystem (~/…​), not on the Windows mount (/mnt/c/…​). File I/O through the Windows mount is significantly slower and causes subtle permission issues.

Curl (All Hosts; Required)

Curl is a command-line utility for making HTTP requests, which is used to download the dxbx script. It is a common and highly recommended tool, but it may not come preinstalled in all Linux distributions, including Ubuntu via WSL2 on Windows.

Test for curl
curl --version
💡
If curl is available, proceed to Docker (All OSes).

If curl not present, install it with your package manager.

Install curl for Debian/Ubuntu
sudo apt-get update
sudo apt-get install curl

Docker (All OSes)

Docker is a platform that allows you to run a containerized Linux environment on your system. Containers are much lighter and more customizable than full virtual machines. They are specifically designed for creating consistent environments for development and automation.

See Threading the needle with Docker, Architecture Decision Rationale, and most usefully the Docker glossary section for more context. This section covers the nuts and bolts of installation.

Docker on Windows (WSL2)

💡
Follow that guide, then move on to DocOps in a Box: Quick-start Guide.

Docker on MacOS

The preferred method is the Docker Desktop installer.

Alternatively, use Homebrew. Follow these instructions to install Homebrew if needed as well as full instructions for installing and starting Docker.

Docker on Linux (non-WSL)

⚠️
Linux users running under WSL2 should install Docker according to Docker on Windows (WSL2). Do not install Docker directly onto the WSL2 host instance.

Install Docker Engine using Docker’s official convenience script:

curl -fsSL https://get.docker.com | sudo sh

The script prints progress and some informational notes about alternative configurations, which you can ignore. When it finishes, enable Docker to start automatically at boot and start it now:

sudo systemctl enable --now docker

Then grant your user permission to run Docker without sudo:

sudo usermod -aG docker $USER
📎
For distro-specific or manual installation, see Docker’s official install docs.
Verify Docker is working
docker --version && docker compose version

Both commands should print a version number with no errors before you continue.

DocOps in a Box: Quick-start Guide

If you have all the prerequisites in place, the recommended quick-start procedure is to use dxbx and a DocOps Box work image to get a containerized environment up and running in just a few minutes.

Quickest Start

In any directory you maintain docs in…​

curl -fsSL https://raw.githubusercontent.com/DocOps/box/refs/heads/latest
scripts/dxbx-bootstrap.sh -o dxbx-bootstrap.sh
chmod +x dxbx-bootstrap.sh
sh ./dxbx-bootstrap.sh
dxbx init
rm dxbx-bootstrap.sh
dxbx ex docops info

For a more detailed, explanatory walkthrough of the same procedure, see step through the next sections.

If this quickest start set up worked for you, skip to Step 4: Start working.

Step 1: Install dxbx (once, system-wide)

If you have not installed dxbx yet, copy, paste, and enter this once in your host shell:

curl -fsSL https://raw.githubusercontent.com/DocOps/box/refs/heads/latest
scripts/dxbx-bootstrap.sh -o dxbx-bootstrap.sh
chmod +x dxbx-bootstrap.sh
sh ./dxbx-bootstrap.sh
What this procedure does
  1. Downloads the dxbx-bootstrap.sh script to the current directory

  2. Makes the script executable

  3. Executes the bootstrap procedure, which performs the following steps:

    1. Checks for Bash 4, curl, and Docker, and prompts you to install any missing prerequisites

    2. Installs dxbx to ${XDG_BIN_HOME} (defaults to ~/.local/bin/dxbx) and adds it to your PATH🔖 if not already present

  4. Prints instructions to activate dxbx in your current terminal session

  5. Cleans up by removing the downloaded dxbx-bootstrap.sh script

💡
When any script/command prompts for [y/N] or [Y/n], it is requesting approval (y for yes; n for no). The capital letter indicates the default selection, so you can just press Enter instead of typing the character to choose the default.

If ${XDG_BIN_HOME} (or ~/.local/bin if XDG_BIN_HOME is not set) is not yet in your PATH🔖, the installer will prompt you to add it automatically; answer y.

Then activate it in your current session by running the export command it prints, or simply open a new terminal window.

Step 2: Initialize your project (optional)

💡
Skip this step if your project is already configured for DocOps Box use. If you do not know, there is no harm in trying.
💡

It is always advised to stash or commit changes using Git before installing new software in any repository.

git stash push -m "Stash before initializing DocOps Box"

In your project directory:

dxbx init

This downloads docopsbox.yml and .env into .config/, makes .devcontainer/devcontainer.json, prompts for a project slug, and updates .gitignore automatically.

Check for new files
ls -a .config/ .devcontainer/

Step 3: Test dxbx with default image

To test that dxbx is working, run this command in your terminal:

dxbx ex docops info

You should see a nicely formatted readout of available tools and be returned to your host shell prompt.

What was that?]

The second half of the above command (docops info) is run inside any DocOps Box container for a readout of tool availability and versions.

The first half (dxbx ex) is the command for executing any command in a one-off container, which is created, run, and removed automatically.

Step 4: Start working

When you need the tools provided by DocOps Box, invoke them inside the container, not on your host system. Typically, you will likely wish to work inside the container interactively, either in VS Code or dedicated terminal app.

Choose whichever entry point suits you; all give you the same environment, and of course you may alternate.

Try dxbx up to experience a full interactive shell session inside the container.

Use dxbx vsc to open a VS Code window with the containerized environment.

Or use dxbx ex to run any command in a fresh container and return to your host prompt immediately.

See the next section for more orientation and explanation of the different ways to work with your new environment.

Using Your New Environment

As much as DocOps Box tries to abstract away the complexities of Docker, some familiarity with the underlying concepts and commands is helpful for troubleshooting and advanced use.

There are also various ways to invoke these environments in your day-to-day work.

Understanding Your Shells

The first thing that confuses new users is that you now have two command environments to keep track of: the host shell (your normal terminal) and the container shell (the Linux environment inside Docker, where your tools actually live and run).

Your computer operating system or shell is the host system.

You are virtualizing a purpose-built Linux operating system in a container, which exploits your host’s Linux/Unix kernel.

📎
If you are using all of this inside WSL2 on Windows, ignore the fact that your Windows OS is also a host. For our purposes, the Linux shell you are running is your host, and Windows is your OS.

So while you may sometimes have two (or more) command prompts to consider, we will specify the host (where you run dxbx commands) or the container (where you run DocOps tools).

Instructions for any third-party Ruby or other CLI utilities (including other DocOps Lab apps) should be performed from within a DocOps Box container.

Mac and Linux users have two layers:

Terminal app  →  container shell (Zsh)
   (host)

Windows users via WSL2 have three layers; you are virtualizing twice:

Windows Terminal  →  WSL2 Linux shell  →  container shell (Zsh)
  (PowerShell)             (host)

The important thing for Windows users: dxbx commands are typed in the WSL2 shell, not in PowerShell or a Windows Command Prompt. WSL2 is your host for everything that follows.

Table 1. How you get into the container
Method How to enter What happens to your prompt

VS Code + Dev Containers

Enter dxbx vsc in your host shell. Or run VS Code through our OS, open a project, Ctrl+Shift+P then select Dev Containers: Reopen in Container if necessary

Terminal in the Dev Container window runs inside the container.

Interactive shell

Enter dxbx up in your host shell

Your prompt changes to the container shell. You stay there until you type exit.

Get in and out

Enter dxbx ex followed by any command in your host shell

Runs the command in a fresh container, prints the output, and removes the container. No change to your prompt.

💡
Not sure which shell you are in? Run hostname. If it prints a short hex string (like a3f9d2b1), you are inside the container. If it prints your machine name, you are on the host.

Some programs you can run inside the container have their own interactive shells (like irb for Ruby or node for Node.js).

Use the following table to determine what to type to detect or exit a shell.

Scenario Prompt Command

Detect PowerShell (Windows only)

PS C:\Users\name>

You are in PowerShell, not yet in Linux. Type wsl to enter your WSL2 shell.

Detect WSL2 shell (Windows only)

username@hostname:~$

You are in WSL2 Linux. Confirm with uname -a; the output will contain microsoft if you are in WSL2.

Exit WSL2 back to PowerShell (Windows only)

username@hostname:~$

exit returns you to the PowerShell prompt if you entered WSL2 by typing wsl. If Windows Terminal opened WSL2 directly as a tab, exit closes the tab instead.

Detect container vs host

appuser@work:/workspace%

hostname prints a short hex string (container ID) if inside the container, or your machine name if on the host.

Exit container to host shell

appuser@work:/workspace%

exit (returns you to your host shell)

Exit irb to container shell

irb(main):001>

exit or Ctrl+D

Exit node to container shell

>

exit or Ctrl+D

Exit python to container shell

>>>

exit() or Ctrl+D

Volume Lifecycle

DocOps Box uses named volumes🔖 to persist data across sessions.

Your shell-command history is preserved in a volume that is used across all projects, so your command history is conveniently available for navigation and autocomplete.

Dependency packages are maintained for convenience, so they need not be reinstalled every time you start a session.

docops-shell-history

Mounts at /commandhistory. Shared across all projects. Contains your Zsh history. Do not delete without backing up first.

docops-<slug>-bundle

Per-project Ruby gem cache. Mounts at /usr/local/bundle. Safe to recreate at any time; bundle install repopulates it.

docops-<slug>-node

Per-project Node.js packages. Mounts at /workspace/node_modules. Safe to recreate; npm install repopulates it. An empty node_modules/ directory will appear in your project root. This is Docker’s mount point; the actual contents are inside the named volume and invisible to the host.

docops-<slug>-python

Per-project Python virtual environment. Mounts at /opt/venv. Safe to recreate; pip install -r requirements.txt repopulates it. Run pip install normally inside the container; no flags needed.

Volume Management Commands

If your volumes ever become problematic or stale for any reason, it is safe to remove the disposable ones, which will be recreated on demand.

Command Effect

dxbx stat

Show container state and volume sizes.

dxbx wipe --vols

Remove the container and all per-project dependency volumes (Ruby gems, Node packages, Python venv); preserve shell history.

dxbx wipe --hist

Remove dependency volumes and shell history (requires double confirmation).

dxbx back

Copy shell history to ~/.local/share/docopslab/dxbx/backups/ before destructive operations.

dxbx back --restore

Restore shell history from a backup (append or replace).

Per-invocation Image Overrides

In most cases, you will likely only need a single image for starting containers on your local machine. Thus, the main invocation commands will (dxbx up, dxbx ex) execute a container based on the default image.

In case you wish to use an alternate image/container, dxbx up, dxbx ex, and dxbx pull subcommands all accept an optional image specifier as their first argument. This selects the image for that invocation without editing any files.

Form Selects

min or max

Variant only; context from config

work or live

Context only; variant from config

max:live, max live

Both variant and context

box-min, box-max

Same as min/max (the box- prefix is accepted)

📎
It is not possible to run a live image in interactive mode. Live images are intended for one-off (dxbx ex CMD) commands.

Example commands

Run a one-off command in the min:live image (no running container needed)
dxbx ex min:live bundle exec rake test
What just happened?

The above command runs bundle exec rake test in a new container based on the bare-bones box-min:live image.

Start an interactive session explicitly in the max:work image
dxbx up max:work
What just happened?

The above command starts a new container based on the box-max:work image and gives you an interactive shell inside it.

In a default environment, this is the same as just running dxbx up with no image specifier.

Test a script on the Ruby 3.4 image
dxbx ex work 3.4 ruby ./scripts/test-script.rb
What just happened?

The above command runs ruby ./scripts/test-script.rb in a new container based on the box-work:3.4 image, which has Ruby 3.4 installed.

Shell-level environment variables (IMAGE_CONTEXT, IMAGE_VARIANT) also perform this function.

📎

If you alternate between Ruby versions, you will need to install dependencies for each version. The Bundler volume will remain the same, but the gems will be installed in separate subdirectories per Ruby version.

The settings for PROJECT_SLUG and IMAGE_REGISTRY can only be overridden via environment variables.

Adding Runtime Dependencies

Inside the container, do not install tools with one-off gem install, npm install -g, or pip install commands (even though it will work). This also goes for shell programs; avoid using apt-get install or similar dependency manager🔖 commands to add tools to the image.

Instead, declare any runtime libraries in a manifest file🔖 and let the package manager install the whole set. This best practice keeps your environment reproducible: anyone on your team (or a CI/CD operation) gets identical versions from the same manifest.

Runtime Manifest file Install command Notes

Ruby

Gemfile

bundle install

Gems persist in the docops-<slug>-bundle named volume. Add gems with bundle add <gem> or edit Gemfile directly.

Node.js

package.json

npm install

Packages persist in the docops-<slug>-node named volume. Add packages with npm install <pkg> --save.

Python

requirements.txt

pip install -r requirements.txt

Packages persist in the /opt/venv named volume. Add packages with pip install <pkg> then freeze: pip freeze > requirements.txt.

These install commands run automatically at container creation inside VS Code. You only need to re-run them manually if you edit the manifest mid-session.

💡
If a tool you need is not available as a gem, npm package, or pip package, it may already be installed system-wide in the image (Pandoc, Vale, Git, etc.). Check with which <tool> before reaching for a package manager.

If your package files are committed in Git and you don’t want to force your whole team to install a tool you need temporarily, you can add it to the image with a custom image build or Dockerfile. See Extending DocOps Box.

For the most part, the whole Docker-based approach of DocOps Box is intended to stick to a per-codebase strategy. While it is nice to have configuration-free tools like Pandoc and Asciidoctor available anywhere in your host shell, even Docker and dxbx cannot abstract away the complexity of persistence throughout a host system.

If you do need more tools directly installed in a Docker image, consider Extending DocOps Box.

Open a server port

If your project includes a server component (like Jekyll’s bundle exec jekyll serve), you can expose the port to your host machine using properties in the configuration files.

Uncomment and modify in .config/docopsbox.yml
services:
  docops:
    ports:
      - "4005:4005"

For VS Code Dev Containers, the port also needs to be forwarded in the Dev Container configuration.

Uncomment and modify in .devcontainer/devcontainer.json
"forwardPorts": [4005]

Be sure the serve command inside the container is configured to bind at 0.0.0.0, and the port is specified to match the forwarded port.

bundle exec jekyll serve --host 0.0.0 --port 4005

Once the server is running, you can access it from your host machine at http://localhost:4005 (or whatever port you forwarded).

Configuration Reference

DocOps Box configuration involves 4 main files:

  • .config/.env (committed to Git; non-secret configuration)

  • .config/.env.local (not committed; secret configuration)

  • docopsbox.yml (committed; Docker Compose configuration)

  • .devcontainer/devcontainer.json (committed; VS Code Dev Container configuration)

By design, you may never need to touch these just to maintain a working environment. The dxbx init command sets them up for you, if you are starting a new project or adding DocOps Box to an existing project.

If you are joining a project, hopefully these files have been established and shared with you.

In cases where configuration tweaks are needed, most can be done in the .env or .env.local files, which are straightforward.

Environment Variables (.config/.env)

The .config/.env file is committed to your repository. It contains only safe, non-secret project-specific configuration.

Variable Default Description

PROJECT_SLUG

see below

Short identifier used to name the per-project gem volume. Set this to something unique across your projects.

IMAGE_VARIANT

max

max (full toolset, adds Node.js and Python) or min (core docs tools: Ruby, Pandoc, Vale, Git). Shell environment (Zsh vs Bash) is controlled by IMAGE_CONTEXT, not variant.

IMAGE_CONTEXT

work

work (interactive, OhMyZsh) or live (automation, Bash only).

RUBY_VERSION

(unset)

Selects a specific Ruby version. Omit (or leave unset) to use the default (3.3).

IMAGE_REGISTRY

docopslab

Docker Hub username or registry prefix for the image.

The PROJECT_SLUG environment variable is used to create unique volume names for each project, so that dependencies installed in one project do not interfere with those in another. It defaults to the parent directory name, but you can set it to any short identifier you like.

📎

RUBY_VERSION also controls which pre-built image tag is pulled from Docker Hub. Set it to select a non-default Ruby version without rebuilding anything.

Secret Environment Variables (.config/.env.local)

The .config/.env.local file is created during dxbx init.

Uncomment values as needed. This file is never committed to Git.

Variable Use

HOST_UID

Override the UID🔖 used for file ownership inside the container. Set to match id -u on the host if you see permission errors.

HOST_GID

Same as above for group ID (id -g).

IMAGE_REGISTRY

Override to a private registry or local mirror.

Project Slug Resolution Order

The dxbx utility resolves the PROJECT_SLUG in this order:

  1. PROJECT_SLUG from .config/.env or .config/.env.local, if present, or else

  2. :this_proj_slug: AsciiDoc attribute in README.adoc, if present, or else

  3. current directory name (spaces and underscores to hyphens, all lowercased)

Image Variants and Contexts

The image tag format is: <registry>/box-<variant>:<context> or, for a specific Ruby version, <registry>/box-<variant>:<context>-<ruby>.

For example: docopslab/box-max:work or docopslab/box-max:work-3.4

Setting Values Notes

IMAGE_VARIANT

max, min

min includes Ruby, Bundler, Git, Pandoc, and Vale. max adds Node.js, Python, and auxiliary tools.

IMAGE_CONTEXT

work, live

work configures Zsh with Oh My Zsh for interactive daily use; live uses Bash only, with no interactive enhancements; suited for CI/CD automation.

RUBY_VERSION

3.3, 3.4

Selects a specific published Ruby version; omit to use the default (3.3). Setting this appends the version to the context tag: work becomes work-3.4.

Docker Compose Configuration (docopsbox.yml)

The .config/docopsbox.yml file is the single source of truth for the container structure. Both dxbx and .devcontainer/devcontainer.json reference it, and docopsbox.yml in turn references .config/.env and .config/.env.local as needed.

Key configuration

  • env_file: loads .env first, then .env.local (required: false; no error if absent; both resolved relative to .config/)

  • Service name: docops; container name: docopsbox_${PROJECT_SLUG}

  • Volume names embed PROJECT_SLUG for per-project isolation

  • Working directory inside the container: /workspace

VS Code Dev Container Configuration (.devcontainer/devcontainer.json)

The .devcontainer/devcontainer.json file configures how VS Code connects to the container. Hopefully, you should never need to edit this file, as it references docopsbox.yml for all relevant configuration.

Use the Development Containers standard documentation for any necessary tweaks or troubleshooting.

Troubleshooting

Start with Diagnostics

Run this first when something is not working.

dxbx stat

This covers the most common problems:

  • Docker Engine version and availability

  • Docker Compose version

  • Image presence and tag verification

  • Volume existence and size inspection

  • WSL2 environment detection

  • Actionable error messages for common failure conditions

Common Issues

1: Error: No such service: docops

dxbx is not running from the directory containing .config/docopsbox.yml.

  1. Change to the project root and try again, or

  2. Use dxbx init to set up the configuration files.

2: Permission denied writing files on Linux

Files the container writes are owned by the container user, which may differ from your host user. Set HOST_UID and HOST_GID in .config/.env.local to match your host user:

# Find your host UID and GID
id -u  # prints your UID
id -g  # prints your GID
config/.env.local
HOST_UID=1001  # replace with output of: id -u
HOST_GID=1001  # replace with output of: id -g

Then re-run dxbx up.

3: bundle install fails inside the container
  1. Be sure Gemfile is in the mounted project root.

    ls -l /workspace/Gemfile
  2. Be sure your present working directory is /workspace inside the container.

    pwd
  3. The bundle volume may have stale or corrupted gem data. Clear and rebuild it:

    dxbx wipe --vols
    dxbx up
    # Inside the container:
    bundle install
4: Gems fail to load after updating Ruby version

Native gem extensions (compiled C code inside gems like nokogiri or ffi) are compiled for a specific Ruby version. If the image’s Ruby version changes and you are using a cached gem bundle volume, the extensions will be incompatible.

Clear the bundle volume and reinstall:

dxbx wipe --vols
dxbx up
# Inside the container:
bundle install

If you are not sure which Ruby version the cached bundle was built against, dxbx stat shows the current image’s Ruby version.

5: VS Code Dev Container fails to start
  1. Verify Docker is running: docker info

  2. Validate the Compose file: docker compose config

  3. Open the Dev Container log: Command Palette → Dev Containers: Show Container Log

6: WSL2: very slow file I/O

Ensure your project directory lives inside the WSL2 filesystem (~/…​), not on the Windows mount (/mnt/c/…​). Windows filesystem mounts have significantly degraded I/O performance under WSL2.

Extending DocOps Box

This advanced topic is only cursorily covered here, but DocOps Box is designed to be extended and customized in various ways.

There are several currently undocumented features, including the use of pre- and post-build scripts and serial image builds with custom Dockerfiles.

This section will grow as real-world conditions prove out use cases.

Custom Image Builds

The Dockerfile used to build supported images is highly configurable. It accepts lots of arguments for a custom build.

You will want to clone the DocOps/box repository and run docker build inside it.

📎
This means you will need Git installed directly on your host (MacOS | Linux/WSL).

Use docker build arguments

  1. Clone the repo (outside your project directories).

  1. With SSH:

    git clone git@github.com:DocOps/box.git docops-box
  2. Without SSH:

    git clone https://github.com/DocOps/box.git docops-box
    1. Enter the new directory.

      cd docops-box
    2. Build a custom image with your preferred build arguments.

      Example build command with some common arguments
      docker build --network host \
        --build-arg IMAGE_CONTEXT=work \
        --build-arg RUBY_VERSION=3.4 \
        --build-arg ADD_NODEJS=true \
        -t docops-box/custom:work \
        .

The -t argument tags the image. The . as the last argument indicates that the Dockerfile is in the current directory.

See the ARG directives in the base Dockerfile for all available build arguments.

Quickie builds with dxbx commands

The dxbx utility, executed in the DocOps Box repository codebase, can build several custom permutations locally.

The format is:

[EXTRA_ARGS ].bin/dxbx make [variant] [context] [ruby_version]
Example image build commands
Builds docopslab/box-min:work with Ruby 4.0
./bin/dxbx make 4.0 min work
Builds docopslab/box-max:live with Ruby 4.0 and Node.js
ADD_NODEJS=true NODEJS_VERSION=26 ./bin/dxbx make min live 4.0

Use a custom image with dxbx and Dev Containers

If you do build a custom image with additional software pre-installed, you can run it from anywhere using a complete docker run command. For instance:

docker run --rm -it -v $PWD:/workspace docopslab/custom:work asciidoctor -o readme.html -a doctype=book README.adoc

If you want to use dxbx commands and/or wish to share it with your team, do the following.

  1. Keep the image tag consistent with its closest DocOps Box variant, but use your organization’s own registry prefix.

    docker build -t ourco/box-min:work .
  2. Host the image on DockerHub.

  3. Change the registry property in your .env to point to your account instead of docopslab.

This should execute dxbx commands on images sourced and built as you choose.

Appendices

Appendix A: Who is This Software For?

I made the DocOps Box toolkit for people who want to learn my preferred document operations automation tools but who do not yet want to deal with properly installing and maintaining Ruby, Node.js, Python, Pandoc, Vale, Git, and more on their system.

If you are less technical than programmers but bolder and more experienced than most peers in your profession, this entire project is geared toward getting you up and running with tools to bridge the gap.

This solution also provides an environment to share amongst your team, or even to use on a production server or continuous-integration/deployment process, without everyone having to install all the dependencies one by one.

If you are working solo, it’s even simpler to get started. Install the prerequisites, install dxbx, and run dxbx init in your project directory. The default setting should work for you, and you can start running commands right away.

💡

Once you are working regularly with these technologies, it may well make sense to set up a proper development environment locally, especially if you find yourself directly invoking command-line tools frequently for similar or parallel projects. This guide provides instructions for doing just this on all three major operating systems.

The repo includes a command-line application (dxbx), mainly for simplifying Docker commands.

Docker is an incredibly powerful and somewhat complicated piece of software, but we use it in specific ways that do not require mastery or even a full grasp of what Docker is and does. More importantly, the dxbx script simplifies the commands you will need to run for this specific Docker use case, without pretending to be a broad controller for other Docker use cases.

This “abstraction” manages Docker images and containers such that the running container will:

  • reflect your host workstation user and group, Git config, and SSH keys

  • work seamlessly with your local Git environment if you already have one

  • write to your own (host/workstation) filesystem so your work remains available when the container shuts down

  • persist the state of your project, including dependencies and command history, across container restarts

Appendix B: Glossary

Terms of art used throughout this document and the broader “DocOps”/docs-as-code domain.

Domain jargon and idioms

docs-as-code

A set of practices for managing technical documentation with the same tools and workflows as software development. This includes using version control (Git), writing in lightweight markup formats (like AsciiDoc or Markdown), and automating builds and deployments with CI/CD pipelines. The goal is to treat documentation as a first-class citizen in the development process, improving collaboration, versioning, and quality.

DocOps

Short for document operations, a set of practices and tools for managing technical documentation with the same (or analogous) techniques and tools as software development.

DocOps tends to be focused on automation and tooling; it pertains to “practitioner services” aspect of the docs-as-code practice. DocOps Box is so-called as it aims to assist document operators by providing the underlying infrastructure for executing a docs-as-code workflow.

bootstrap

A process for rapidly initializing or instantiating a project/codebase from minimal inputs. The process typically involves “inflating” files from templates and applying enough configuration to enable basic/nascent operations.

In the world of CLI tools and shell environments, “bootstrapping” often means performing an init command that writes files and prepares for or executes a first invocation of whatever program is being set up.

technical documentation

Documents that are structured, versioned, highly semantic, single sourced, divergently delivered, or otherwise complex to the extent of requiring special handling for the management, maintenance, or automated/repeat publishing. This includes legal documents, standards specifications, product requirements, reference materials, non-linear narratives, curriculum, as well as any “living” document, collaboratively authored material, or anything intended to diverge from but maintain association with a prime or peer document.

tech stack

The core, categorical or platform-level components of a project or workflow. DocOps Box is optimized for an AsciiDoc, YAML, and Liquid tech stack.

toolchain

A set of software tools that work together to accomplish a task. In the context of DocOps Box.

Docker and container concepts

Docker image

A read-only, pre-built package containing an operating system layer and pre-installed software. When you run an image, Docker creates a live container from it. DocOps Box provides several pre-built images tagged by variant and context (for example, box-max:work).

container

A running instance of a Docker image. The container is isolated from your host system but can read and write files through a bind mount. When the container stops, any changes made outside of mounted paths or named volumes are discarded.

named volume

A persistent storage area managed by Docker, separate from both your project directory and the container’s own filesystem. DocOps Box uses named volumes to store installed dependencies (Ruby gems, Node packages, Python packages) and your command history, so they survive container restarts. Unlike a bind mount, a named volume is invisible on the host; its contents are accessible only from inside a container.

bind mount

A direct link between a directory on your host and a path inside the container. DocOps Box bind-mounts your project directory to /workspace so your files are accessible inside the container without copying. Changes on either side are immediately reflected on the other.

Docker Hub

Docker’s public registry for sharing and downloading container images. DocOps Box pre-built images are hosted there under the docopslab organization. When you pull an image for the first time, Docker Hub is the source.

registry

A server that stores and distributes Docker images. Docker Hub is the default public registry. The IMAGE_REGISTRY variable lets you specify a private or alternate registry.

image tag

The label appended after the colon in an image name. For example, the work in docopslab/box-max:work. Tags identify the specific variant, version, or configuration of an image.

Docker Compose

A tool for defining and running Docker containers using a YAML configuration file. DocOps Box uses a minimal Compose file (docopsbox.yml) so that container configuration (volumes, environment variables, user IDs) is version-controlled and shareable across a team.

System and shell concepts

host / host system

Your computer’s own operating system and filesystem; the environment you are in before entering a container. Commands like dxbx up are host commands. Commands like bundle install run inside the container, not on the host.

shell

A program that accepts text commands and passes them to the operating system to execute. Bash and Zsh are both shells. When you open a terminal, you are running inside a shell. DocOps Box work images use Zsh with OhMyZsh; live images use Bash.

PATH

An environment variable that lists the directories your shell searches when you type a command name. If dxbx cannot be found after installation, your PATH does not yet include the installation directory: ${XDG_BIN_HOME} (which defaults to ~/.local/bin if XDG_BIN_HOME is not set). dxbx install will offer to add the required line to your shell profile automatically.

UID / GID

Representing user identifier and group identifier, numbers the Linux kernel uses to track file ownership and access permissions. Every file belongs to a UID and a GID. If files the container writes appear as owned by an unexpected user, set HOST_UID and HOST_GID in .config/.env.local to match the output of id -u and id -g on your host.

SSH / SSH agent

SSH (secure shell) is the protocol used for encrypted communication between computers, including authenticating to GitHub for pushing and pulling code. The SSH agent is a background process that holds your decrypted SSH keys so you do not have to type your passphrase repeatedly.

Executing dxbx up automatically forwards the host SSH agent into the container when SSH_AUTH_SOCK is set in the host shell. SSH agent forwarding in VS Code Dev Containers requires per-OS configuration; see the comments in .devcontainer/devcontainer.json.

entrypoint script

A script that runs automatically each time a container starts, before the main process. DocOps Box’s entrypoint detects the HOST_UID and HOST_GID environment variables and adjusts the container user’s identity to match, ensuring files you create inside the container are owned by you on the host.

postCreateCommand

A VS Code Dev Containers configuration hook that specifies a command to run automatically the first time a container is created. DocOps Box uses it to run bundle install, npm install, and pip install so declared dependencies are ready immediately after the container starts, without any manual step.

Runtime and dependency management

runtime / runtime environment

The software layer responsible for executing programs written in a specific language. Ruby, Node.js, and Python each have a corresponding runtime that must be installed to execute programs written in that language. DocOps Box images bundle the runtimes you need so you do not have to install them individually on your host.

dependency manifest file

A document that declares a project’s dependencies and (optionally) the specific versions required. Examples: Gemfile (Ruby), package.json (Node.js), requirements.txt (Python). A dependency manager reads the manifest to install exactly the right packages.

dependency manager / package manager

A tool that reads a manifest file and installs the listed packages at compatible versions. Examples: Bundler (bundle install) for Ruby, npm for Node.js, pip for Python. In DocOps Box, installed packages are stored in named volumes so they survive container restarts.

gem

A Ruby software package, distributed via RubyGems and managed by Bundler. When you run bundle install, Bundler reads your Gemfile and installs the declared gems into the project’s named volume.

version manager

A tool that allows multiple versions of a runtime (Ruby, Node.js, Python) to coexist on a single machine and be switched per project. Examples: rbenv and RVM for Ruby; nvm for Node.js; pyenv for Python. DocOps Box handles versioning through its pre-built images. Version managers are only relevant if you install runtimes directly on the host.

Abbreviations and tooling terms

CI/CD

Stands for continuous integration/continuous deployment (or delivery), an automated pipeline that builds, tests, and optionally publishes your project every time you push/merge changes into the main branch. DocOps Box live images are designed for use in CI/CD environments.

CLI

Short for command-line interface. Refers to any prompt-based program you interact with by typing commands into a terminal, as opposed to clicking through a graphical presentation. Most tools in the DocOps ecosystem are CLI tools. CLI is effectively a superset which includes TUI applications.

TUI

For text-based user interface. An application that runs inside a terminal but presents a structured, screen-filling layout; more than a plain command prompt, less than a graphical window. The text editors included in DocOps Box images, nano and Vim, are TUI applications.

OAS / OpenAPI Specification

A standard machine-readable format for describing or defining REST APIs (formerly named Swagger). OAS documents (OAD) are typically written in YAML and used to generate API documentation, client libraries, and testing regimens.

IDE

Stands for integrated development environment, an application that combines a code editor with tools for navigating, building, and debugging software. VS Code is the IDE recommended by DocOps Box; it integrates with Docker through the Dev Containers extension.

Appendix C: Auxiliary Tools

The following tools are available inside every *max:work image:

Tool Purpose CLI

Asciidoctor

Converts .adoc to HTML, PDF, and more

asciidoctor, asciidoctor-pdf

Kramdown-AsciiDoc

Converts .md to .adoc

kramdoc

Nokogiri

Parsees and manipulates HTML and XML

nokogiri

Tilt

Renders ERB, Liquid, and many more template formats

tilt

Pandoc

Document conversion/migration

pandoc

Vale CLI

Linting and style checking

vale

Redocly CLI

OpenAPI validation and docs generation

redocly

Speakeasy CLI

OpenAPI validation and mock servers

speakeasy

Vacuum

OpenAPI document manipulation

vacuum

LibreOffice CLI tools

Document conversion and manipulation

soffice, unoserver

jq

Command-line JSON processor

jq

yq

Command-line YAML processor

yq

These are container-shell commands. From your host terminal, use dxbx up to start an interactive container session and then invoke tools at the prompt, or use dxbx ex <command> to run a single tool directly without entering the container shell.

Sample Commands: Asciidoctor

Convert all .adoc files under docs/ into build/
asciidoctor -R docs -D _build docs/**/*.adoc
Generate a styled, navigable manual (TOC, numbered sections)
asciidoctor -a toc=left -a sectnums \
 -a source-highlighter=rouge -o _build/manual.html \
 README.adoc
Produce a PDF from a single AsciiDoc source
asciidoctor-pdf -a toc -a pagenums -o _pubs/manual.pdf manual.adoc

Sample Commands: Kramdown-AsciiDoc

Convert a Markdown file to AsciiDoc
kramdoc -o README.adoc README.md

Sample Commands: Nokogiri

Use Nokogiri’s CLI mode to parse and manipulate HTML and XML documents.

Count significant headings in a remote page
nokogiri https://docopslab.org/docs/contributing/ \
 -e 'puts $_.css("h1,h2,h3").count'
List all links on a page (text + URL)
nokogiri https://docopslab.org/docs/contributing/ \
  -e '$_.css("a[href]").each { |a| puts "#{a.text.strip}\t#{a["href"]}" }'

Sample Commands: Tilt

Use Tilt’s CLI mode to render templates in various formats.

Test rendering of inline templates
echo 'Hello, <%= name %>!' | tilt -t erb --vars='{name: "world"}'
echo 'Hello, {{ name }}!' | tilt -t liquid --vars='{name: "world"}'
List all available engines
tilt --list

Sample Commands: Redocly CLI

The Redocly CLI provides utilities for working with OpenAPI documents.

Validate an OpenAPI document
redocly lint api.yaml
Generate static HTML API docs
redocly build-docs api.yaml -o _build/api-docs.html

LibreOffice tools

The LibreOffice tooling in work containers is primarily intended for use in CI/CD pipelines, where you can invoke the live image directly with docker run or via dxbx ex to perform conversions and other operations on demand.

⚠️
This section is entirely generated using Lumo LLM, and has not been verified for accuracy. My clients sometimes need these tools, but I have not worked with them.
Key capabilities comparison
Feature soffice unoserver

Document opening/creation

✅ Full support

❌ Not directly

File conversion

--convert-to

✅ Via server API

Print operations

-p, --pt, --print-to-file

❌ Not directly

Headless mode

--headless

✅ Implicit (daemon mode)

Network/API access

⚠️ Via --accept

✅ Built-in XMLRPC/UNO servers

Daemon/background mode

❌ Limited

--daemon flag

Request limits

❌ No

--stop-after

Conversion timeouts

❌ No

--conversion-timeout

Logging control

-f, --verbose, --quiet

✅ Same options

Port configuration

❌ Manual via --accept

--port, --uno-port

Operation soffice unoserver

Print to PDF

soffice --headless --print-to-file output.pdf input.doc

Batch processing

Looping soffice calls in a script

unoserver --daemon + API calls for each file

CI/CD pipelines

soffice invoked in build scripts

unoserver running as a service, API calls in build scripts

Scripted migration

soffice --convert-to in a script

unoserver API calls for conversion in a script

Use soffice for:
  • Converting files in a simple script (--convert-to pdf *.doc)

  • Printing documents directly

  • Running occasional batch operations

  • Executing macros on specific files

  • Quick one-off document operations

Use unoserver when you need:
  • A persistent service handling multiple conversion requests

  • Centralized logging and monitoring

  • Timeout protection for stuck conversions

  • To build an application that programmatically calls LibreOffice

  • To limit resource usage (--stop-after, --conversion-timeout)

  • Network-accessible document conversion

Sample LibreOffice commands
Convert a Word document to PDF with soffice
soffice --headless --convert-to pdf input.doc
Execute a macro on a document
soffice --headless "macro:///Standard.Module1.MyMacro(input.doc)"
Convert a batch of mixed text/docs to ODT
soffice --headless \
  --convert-to odt \
  --outdir /workspace/output \
  /workspace/input/*.txt /workspace/input/*.docx
Start unoserver in the background
unoserver \
  --interface 127.0.0.1 \
  --uno-interface 127.0.0.1 \
  --port 2003 \
  --uno-port 2002 \
  > /workspace/unoserver.log 2>&1 &
Convert a document to PDF using unoserver’s API
unoconvert \
  --host 127.0.0.1 \
  --port 2003 \
  --convert-to pdf \
  /workspace/test_in/sample2.docx \
  /workspace/test_out/sample2.uno.pdf
Stop unoserver
kill "$UNOSERVER_PID"
wait "$UNOSERVER_PID" 2>/dev/null || true

Appendix D: Installing Everything to Host

Are you ready to install Ruby and other tools on your workstation or another host so you can stop messing with Docker and dxbx?

💡

If you already know you want to install some or all software directly to your host, skip to the nuts and bolts. The next few sections are for helping you decide.

Whatever your operating system, you are going to have to roll up your sleeves and take several steps. Over time, you will also need to actively configure and periodically upgrade most of what you install here; this is the reality of maintaining a “development environment” on your workstation.

📎
Windows users are still advised to perform Ruby/runtime-centric commands on Linux via WSL2, so these instructions focus on that approach rather than a native Windows install.

Zsh and Git should be more straightforward, if you are not already using them directly on your host workstation.

Ruby can be a bit trickier, but my clients have found this method not to be too frustrating on both MacOS and Linux, including Linux via WSL2 on Windows.

Node.js, Python, and Pandoc are also relatively simple to install and set up, and all three are very likely to be required or at least helpful in your career using developer tools to manage documents and documentation.

For these reasons, you may be well advised to “bite the bullet” and install everything directly on your host, skipping or abandoning Docker, or using it case by case.

Host-side installation does not preclude concurrent usage via Docker. You can use pretty much any mix of native installs and Docker-based fallbacks.

Then again, maybe this is more than you wish to manage.

Why Not?

Let’s go over why you might not want to move away from the Docker method.

If the Docker method is working for you, there may be no real reason to switch.

The Docker method might be almost necessary where lots of frequently changing dependencies are being managed. In fact, if you are already working with a modified docopsbox.yml shared among multiple users, you are probably experiencing the advantages of standardizing around a shared container toolchain: containers that start identically every time, with common commands.

If you are working with someone who is constantly developing Ruby-based tools, they are responsible for helping keep your environment up to date. Especially across multiple runtimes (mixing Python, JavaScript, or who-knows-what in a larger project), containerization may be the only way to keep up.

My own work should not require many changes in this regard, so if you’re following my advice longer term, at least in my case, the whole point is to hopefully keep the dxbx script/box images combination relevant and useful to all my clients.

If the work you’re doing depends entirely on Ruby and shell commands, the Docker method offers less advantage, even though that is also foremost what it is designed for.

💡
Having Git installed on your workstation (host) probably does have big advantages and really no downside. Having it installed on your host will not conflict with the Git installation in the DocOps Box Docker image, either.

If you are using multiple Docker containers or a heavily adapted version of dxbx, it will likely be harder to reproduce and maintain what Docker does for you currently.

In short, if you have not been told to install Ruby, Node.js, Python, Pandoc, and the like directly to host, you probably do not need to.

💡
If you find yourself comfortable using containers run through dxbx, you may want to explore the docker and docker compose commands and the broader Docker “ecosystem”. Some people maintain elaborate development environments in Docker and install very little on their host workstation.

Of course, no harm should be done if you set things up locally and invoke Docker containers as a fallback. The remainder of this appendix is for those who want to set everything they need up on their work machine and any live instances (servers, CI/CD) directly.

Auxiliary tools and host-side installs

Some tools work best when installed directly on your host machine rather than inside the container. Having them available on your host can improve performance, integration with other software, and ease of use.

Similarly, tools that need to affect your host environment outside a particular project directory, simply need to be installed on the host.

This guide advises installing the following tools directly on your host system, even if you leave others to DocOps Box containers.

Git

DocOps Box does its best alias a proper host installation, but it does better when there is a proper host installation. Git is one of the easiest tools to “maintain” and one of the handiest to have installed directly on your host.

Zsh

MacOS users already enjoy Zsh as their default shell, and Linux users can easily install it with their package manager.

VS Code integrations

Language servers, linters, and formatters that VS Code invokes while you type (ESLint, Prettier, the Vale extension’s binary). Install these directly according to their own documentation so your editor can find them.

Tools you call constantly from a terminal

If you find yourself typing dxbx ex asciidoctor …​ or dxbx ex pandoc …​ dozens of times a day, it is probably time to install those tools on your host. Pandoc has minimal host-side dependencies and installs cleanly on MacOS, Linux, and WSL2 without a version manager.

Asciidoctor is better installed through a proper runtime platform, such as Ruby, Node.js, or JVM. This guide recommends Ruby and Node.js.

Creating an alias command in your host shell that invokes these tools via container alias pandoc='dxbx ex pandoc' can be a good middle ground if you don’t want to install them directly on your host but want to avoid typing dxbx ex every time. Frankly, dxbx ex is kept as simple as possible to make this less of an issue.

Having a tool installed both on the host and inside the container causes no conflict. The container’s PATH🔖 is entirely separate; each invocation independently resolves to whichever install is in scope.

Use Everything Natively

If you have decided it is worth the trouble to install the software directly and locally, this is your guide to minimizing overhead.

Advice on the few tough choices involved and some maintenance tips are included.

Windows Users

It is perfectly possible to run all of these programs and environments, and much more, under WSL2 on Windows 10 or 11. This is strongly recommended as the way to maintain an optimal, consistent environment. Skip to the Linux guide to get started.

Truly Native Windows Dev Environment

Alternatively, you can get all of this stuff to run directly on Windows, though I do not recommend that route.

It has become especially possible to run a proper development environment on Windows 10/11, particularly with the GitBBash program that ships with Git for Windows.

Ruby has a Windows installer (choose the latest x64 with Devkit), and it’s supposedly even possible to install Zsh on Windows.

It is far from irrational to install everything on Windows, but it’s also probably not optimal, since WSL2 works so well and aligns your system with the absolute vast majority of professional and open-source coders (including anyone with a Mac).

Because this path is not recommended, this guide will not instruct it. However, the links above are a good place to start.

Otherwise, stick with WSL2, and skip to the Linux guide.

MacOS Users

Mac users will likely have the fewest steps to get everything installed, especially if you already have Homebrew set up.

💡
Homebrew is an essential resource on MacOS. It is the equivalent of an official package manager in Linux distributions, managing dependencies and easing the update process. It also keeps you out of Apple’s sphere of control when it comes to installing and maintaining CLI tools and even GUI apps like VS Code, Slack, Postman, LibreOffice, and more.

MacOS ships with Zsh, and you are probably already using it. I recommend further customizing with OhMyZsh, but otherwise no action is needed.

Likewise, MacoS ships with Git. Try the command git --version to ensure that it is installed.

If not, Homebrew to the rescue!

brew install git

Even though OSX comes with Ruby pre-installed, it is not properly set up for our purposes, and you will likely see lots of errors and be forced to use the sudo command prefix to perform commands as superuser, which is not advised.

Instead, use rbenv or another Ruby management platform, as instructed below.

Linux Users (Including WSL)

Install any packages except Ruby using your package manager.

For example, on Ubuntu- or Debian-based distributions, you can install Zsh and Git with:

sudo apt-get install zsh git

Your distribution likely does not come with Ruby installed, and we do not advise using your package manager to install it. This is likewise true for Node.js.

Most Linux distributions ship with Python 3 installed. See the section in this guide on Python for instructions on managing Python versions if needed.

Use a Ruby management system

Even if your operating system already has Ruby installed, you should reinstall with a version manager. This is especially the case with MacOS, which has weird permissions issues with its native installation.

My personal version manager of choice is rbenv. I have never seen rbenv installation fail using their recommended procedure.

If you are feeling adventurous, rbenv’s maintainer keeps this list of alternative Ruby managers, with good things to say about several of them. I may get around to experimenting with them, but for now my instructions will assume rbenv.

Which Ruby Version?

The default version in the accompanying Docker image is 3.3, which is well-supported across the gem ecosystem. The latest supported version (3.4) can also be used; choose it for new projects.

With rbenv or an equivalent version manager you can keep multiple versions on hand for projects with divergent dependencies. In that case, set different local versions:

rbenv install 3.3
rbenv install 3.4
rbenv global 3.4
cd project-a
rbenv local 3.3
cd ../project-b
rbenv local 3.4

All executions of ruby, bundle exec, etc. will use the locally appropriate Ruby stack. This approach is compatible with the DocOps Box/dxbx method, which uses the right Docker image per project.

Add Ruby gems globally

Once Ruby is installed, you can install any gems you like globally on your host system.

The ones that ship with DocOps Box work images are:

gem install asciidoctor kramdown-asciidoc nokogiri tilt

If you decide not to use the Docker image, you may still wish to use the optional software included in the image. Here we instruct setting up the other runtimes and utilities that are included in the DocOps Box Docker image.

Node.js

Whether you use Node.js as a main runtime environment or not, you will sooner or later surely need the Node Version Manager (nvm) application to manage Javascript assets.

Both nvm and Node.js are best installed using their platform- and installer-specific documentation. Be sure to choose your platform (Linux or MacOS). For the rest, leave default settings, unless you have reason to do otherwise.

📎
Windows users should definitely install these resources on their WSL2 hosting instance, even though there are Windows versions available.

As of now, the Version 24 line is most widely used, including by the DocOps Box images.

If you need multiple versions of Node.js, that’s what nvm is for. Just use commands like nvm install 22 and nvm use 22 to switch between versions.

Python

Most Linux and MacOS distributions come with Python 3 pre-installed, including the Ubuntu and Fedora distributions that work with WSL2 on Windows.

In my experience, the pre-installed Python versions are usually sufficient for the tools we tend to use for docs-as-code. If you need to install or manage Python versions, the pyenv tool is a good choice, with installation instructions for all platforms.

Similarly to Ruby on MacOS, the pre-installed Python on Linux may have permissions issues that make it difficult to work with.

Pandoc

Even if Pandoc is not central to your documentation toolchain, sooner or later it will be just the right tool. It can be especially useful during one-time migrations from one source format to another.

📎
It may be more sensible to install Pandoc directly on Windows in addition to WSL2, just for ease of access.

Pandoc maintains downloads and installation instructions for all operating systems.

Vale

Vale is a prose linter that can be configured with style guides to enforce writing standards.

Follow Vale’s installation instructions for your platform to set it up natively.

LibreOffice CLI

To take advantage of any of LibreOffice’s document manipulation tools or extensions, install the CLI tools.

Support for LibreOffice functionality on the command line comes in two separate tools: soffice and unoconv/unoserver.

Unfortunately, installation of these tools is complicated. Look to the DocOps Box Dockerfile for reference, but novice users may need help with this step. The unoserver command relies on Python, and the soffice command comes with LibreOffice utilities and may already be available or may need multi-step installation.

Redocly CLI

Redocly CLI is a powerful tool for managing OpenAPI documents, including generating API references from them. It runs on your Node.js runtime, so you can install it with npm:

npm install -g @redocly/cli

Other environment customizations

The DocOps Box work images include a handful of quality-of-life tweaks beyond the default Zsh/OhMyZsh setup.

If you are running these tools on your host, you can reproduce any of these you find useful.

Shell: command aliases

Add to your ~/.zshrc (or ~/.bashrc):

alias edit=nano
alias ls="ls -lha --color=auto" # (1)
alias cat="bat --paging=never" # (2)
alias grep="grep --color=auto"
alias egrep="egrep --color=auto"
alias jekyllserve="bundle exec jekyll serve --host=0.0.0.0"
  1. ls with human-readable sizes, hidden files shown, and colorized output.

  2. bat is a syntax-highlighting wrapper for cat; install natively

Shell: directory navigation

Add to ~/.zshrc to enable cd-free navigation and a pushd stack:

setopt AUTO_CD
setopt AUTO_PUSHD
setopt PUSHD_IGNORE_DUPS
Shell: case-insensitive tab completion

Add to ~/.zshrc:

zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
Git defaults

Sane defaults for everyday workflows. Run:

git config --global init.defaultBranch main
git config --global pull.rebase false
git config --global push.autoSetupRemote true
git config --global core.pager cat
nano settings

Create or edit ~/.nanorc:

set linenumbers
set mouse
set tabsize 2
set softwrap
Vim settings

Create or edit ~/.vimrc:

syntax on
filetype plugin indent on
set number

Appendix E: Background and Context

This project emerged from numerous attempts to provide robust runtime environments for clients with complex needs and insufficient capacity to learn or maintain all of these platforms.

Truth be told, many or even most developers cannot or wish they did not have to maintain complicated development environments on their local machines. This is especially true for technical writers and other documentation professionals, who tend to have less experience with and appreciation for command-line tools, package managers, and recursive dependency chains.

Threading the needle with Docker

Short of being a turn-key/magic-wand solution, DocOps Box is meant to be a one-stop-shop, or maybe a first-stop-shop, for getting “up and running” with what I think most people will need to perform powerful tasks with digital documents of various kinds.

There will inevitably be further installation and configuration steps to perform before you’re automating all your document operations. This project intends to get you to that point without much knowledge of Linux, Ruby, Node.js, Python, Git, SSH, or even Docker, for that matter.

The main purposes of technologies such as Docker, Compose, and Dev Containers are to facilitate ready-made environments that are consistent across teams and throughout software-production and -delivery pipelines. In this sense, they are purpose built for some expert to design/configure them to fit a certain situation that may be useful to dozens or thousands of non-experts who just need to perform tasks in a reliable state.

I spent years watching my clients (mostly technical writers on Windows) struggle to get Ruby and its dependencies installed so they could work locally with the platforms I assembled for them.

Docker genuinely fulfils the promise of containerization, providing just-right environments that unify a team around standardized setups. But getting there on your own means choosing the right base image, wiring up volumes, user ID mapping, environment variables, SSH key handling, and a compose file that everyone can actually use.

All of that adds up fast. Without experience and context, even using LLMs (“AI”) to generate Dockerfiles and compose files can lead to very messy results that only compound when shared with a team.

And suddenly all the docker build and docker run commands you need to execute to get going are so complex that they are more of a barrier than the original problem of installing the environment on your host machine.

This is why DocOps Box includes configuration files and a dxbx executable. At least for getting your environment ready, commands should be extremely simple, with all the complexity of Docker and even Compose abstracted away.

Why not the alternatives?

DocOps box attempts to expand this per-application approach to something like a per-domain approach, where the domain is “document operations” for multiple projects or distinct sets of documents. It does not promise to be “all things to all users”, but it does make some big promises. One is that this project is more convenient to new users than the alternatives.

To be certain, let’s explore those alternative means of “#bootstrapping”🔖 a document operations environment, and how we hope to ensure this project is a better way to get up and running.

native installation instructions

There are advantages to native installation of all the tools you need, but it is also the most time-consuming and maintenance-heavy approach. The README file instructs how to install and configure the necessary tools directly on each user’s host machine. This approach tends to require the most active maintenance, and of course it takes users the most time to set up.

See the Installing Everything to Host section for instructions on installing Ruby for an example of how much documentation might be needed just for setup instructions for each project.

virtual machines

Setting up a virtual machine with tools like VirtualBox or Vagrant can provide an isolated environment, but it requires more resources, a steeper learning curve, and more maintenance than Docker containers. VMs are also not ideal for CI/CD pipelines, cloud-based development environments, or quick one-off tasks.

Codespaces

Cloud-hosted development environments like GitHub Codespaces can be configured with a .devcontainer directory in your repository. While Codespaces offers a seamless experience for users with GitHub accounts, it is not universally accessible, especially for those working in private repositories or without GitHub accounts.

Codespaces is also not a local environment, and honestly I just cannot advise working in a cloud system because I cannot fathom working that way myself. It is possible that cloud-first is a better way to learn docs-as-code tools and workflows, but I have not even heard this claim, and I would not be the person to lead such a project.

If anyone knows of other approaches that might be better for providing such a broad swath of technologies in a ready-to-use way, I would love to hear about it.

The dxbx script always shows you the exact Docker commands it runs, which has the handy side effect of teaching you the underlying tooling if you care to learn it. If you advance to the point where docker compose commands feel natural, you can use them directly; nothing about DocOps Box locks you in.

Why Ruby?

It is common to hear Ruby described as somewhat in decline as a programming language. Fortunately, its vast and well-maintained library of tools (typically packaged and published as “gems”) is still in widespread use, and the community is alive and well.

Ruby is the basis for countless excellent command-line tools and APIs, including Asciidoctor, Liquid, Jekyll, Nokogiri, and Guard. Additional tools that are not Ruby-native but have excellent Ruby APIs include Pandoc (via pandoc-ruby) and ImageMagick (via rmagick).

I make no claim that Ruby is somehow the ultimate runtime environment for document automation. Node.js and Python are strong contenders, and a truly complex docs-management system may of necessity incorporate more than one runtime.

Nevertheless, the main thread of my software preferences is Ruby-native, and DocOps Box is directly aimed at getting people up and running with those tools.

The max images carry Node.js and Python alongside Ruby precisely so the environment does not become a dead end if your toolchain requires them. These images are suitable for and tested with numerous Node.js and Python tools.

Why Zsh?

Ever since Apple made Z shell the default shell on MacOS, I have felt confident recommending it to anyone needing a terminal shell. As a superset of Bash, Zsh users can always run Bash scripts and commands without conflict.

Zsh provides a noticeably better user experience out of the box, and OhMyZsh builds on it with sensible defaults:

  • Tab autocompletes commands, filenames, and even CLI arguments.

  • / cycles through your command history; Ctrl+R searches it.

  • Prompt syntax highlighting shows you whether a command is recognized before you press Enter.

These features are technically achievable in Bash with enough plugins and configuration, but Zsh and OhMyZsh make them trivially available from day one. The only downside: you may miss them when stuck at a bare server Bash prompt, which is exactly why live images stay on Bash, where CI/CD environments expect it.

Architecture Decision Rationale

Why Docker + Compose?

Raw docker run commands quickly grow unwieldy: volumes, ports, user IDs, environment variables, image tags, all of them manually specified each time. Docker Compose externalizes all of that into a versioned docopsbox.yml, making the run configuration reproducible and shareable in Git.

Fortunately, the breadth of DocOps use cases does not require a complex multi-service setup, so the Compose file is simple and approachable for users new to Docker.

Why separate work and live contexts?

Interactive daily work benefits from things that automated pipelines do not, such as Z shell, TUI text editors, and full terminal tooling. Including these in a live CI/CD image wastes build time and inflates image size. Separate contexts keep each image lean and purpose-built.

That said, you could also use one max:work image for both purposes early on if you don’t wish to fuss about the distinction.

Why named volumes for all dependency caches?

Dependency trees for Ruby, Node.js, and Python all produce Linux-native artifacts (compiled gem extensions, Node binary modules, Python virtualenv symlinks) that are wrong-architecture or broken when accessed from the host filesystem on MacOS or Windows. Keeping them in named volumes (invisible to the host) removes that failure mode entirely and eliminates bind-mount overhead on MacOS. Keeping dependency files invisible to the host filesystem also keeps searches cleaner than if they were stashed in a directory under the project root.

Dependency caches are disposable and fully reproducible bundle install, npm install, or pip install regenerates them in minutes.

Why an entrypoint script?

Docker images deployed to a registry cannot know the UID or GID of the user who will run them. Placing UID/GID reconciliation in the image’s own ENTRYPOINT script (rather than baking it at build time or requiring a separate file in the user’s project) satisfies all three requirements simultaneously: it runs on every docker run or docker compose run invocation, works with pre-built registry images, requires no extra files in the user’s project directory.

The entrypoint detects the HOST_UID and HOST_GID environment variables (passed in by docopsbox.yml), adjusts the container user’s identity to match, then drops privileges and executes the original command ($CMD or the default shell). On MacOS with Docker Desktop, the VM layer provides transparent ownership mapping and the entrypoint is a no-op (it does nothing and throws no error).

Why no [your runtime here]?

The max images include Ruby, Node.js, and Python because they are the environments I repeatedly find myself using and recommending to clients. Tempted as I am to add Java, Go, and Rust, I have no real experience supporting these platforms and only sporadic need. Go, Haskell, and Rust applications typically compile to static binaries that run just fine on the host, such as Vale and Pandoc. Adding more runtimes would also bloat the image size and build time, and I want to keep the project focused on documentation operations rather than becoming a general-purpose development environment.

Appendix F: Development and Contribution

DocOps Box is maintained by DocOps Lab. Contributions are welcome.

Building Locally

  1. Clone the repo.

    git clone git@github.com:DocOps/box.git
  2. Build a permutation of the image.

    Example work image build with specific Ruby version
    docker build \
      --build-arg IMAGE_CONTEXT=work \
      -t docopslab/box-max:work \
      .

Smoke Tests

For now, execute these sample commands to make sure your new image is working properly.

docker run --rm docopslab/box-mine:work ruby --version
docker run --rm docopslab/box-mine:work bundle --version
docker run --rm docopslab/box-mine:work git --version
docker run --rm docopslab/box-mine:work pandoc --version
docker run --rm docopslab/box-mine:work npm --version
docker run --rm docopslab/box-mine:work python3 --version

Contributing

See our Contributors Guide for general policy and instructions.

Main ways to contribute:

  1. Open an issue for bug reports or feature proposals.

  2. Fork the repository and submit a pull request.

Contributions should:

  • Not break the min:live CI/CD use case.

  • Not increase max image size unnecessarily.

  • Update this README alongside any user-facing changes.

  • Follow the AsciiDoc authoring conventions used throughout this document.

About

DocOps Box provides a ready-made workspace for popular docs-as-code tools.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors