Today I implemented a crucial piece of the Aditi project: integrating Vale with AsciiDocDITA rules through container technology. This post explains the journey from initial requirements to final solution, highlighting key design decisions and technical challenges overcome.
The Challenge
The Aditi project needs to validate AsciiDoc files against DITA compatibility rules before migration. The original plan called for using a custom Docker image (ghcr.io/rolfedh/asciidoc-dita-toolkit-prod
), but I discovered a more elegant solution using Vale’s official container and package system.
Initial Requirements
Looking at the pre-implementation todo list, the container setup was marked as critical for Phase 0. The goal was clear: after installing Aditi from PyPI, users should have Vale running with AsciiDocDITA styles automatically.
The original approach would have required:
- Maintaining a custom Docker image
- Bundling Vale and AsciiDocDITA rules together
- Managing image updates and versioning
- Dealing with registry authentication
The Pivot: Embracing Vale’s Package System
While implementing the container setup tasks, I discovered Vale’s built-in package management system. This led to a cleaner architecture:
- Use the official Vale Docker image (
docker.io/jdkato/vale:latest
) - Download AsciiDocDITA styles dynamically via Vale’s package system
- Leverage existing infrastructure rather than reinventing the wheel
Key Implementation Decisions
1. Podman First, Docker Fallback
def check_container_runtime() -> str:
"""Check which container runtime is available."""
for runtime in ["podman", "docker"]:
try:
subprocess.run([runtime, "--version"], check=True, capture_output=True)
return runtime
except (subprocess.CalledProcessError, FileNotFoundError):
continue
raise RuntimeError("Neither Podman nor Docker is available.")
Podman offers better security with rootless containers by default, so it’s checked first. Docker remains as a fallback for broader compatibility.
2. Fully Qualified Image Names
During testing, I encountered this error:
Error: short-name "jdkato/vale" did not resolve to an alias
The solution was simple but crucial: use docker.io/jdkato/vale:latest
instead of just jdkato/vale
. Podman requires the full registry path, while Docker accepts both.
3. Clean Configuration Management
The Vale configuration template is minimal and focused:
StylesPath = .vale/styles
MinAlertLevel = warning
# Download AsciiDocDITA package from GitHub
Packages = https://github.com/jhradilek/asciidoctor-dita-vale/releases/latest/download/AsciiDocDITA.zip
[*.adoc]
BasedOnStyles = AsciiDocDITA
This configuration:
- Points to the official AsciiDocDITA release
- Automatically downloads the latest rules via
vale sync
- Applies rules only to AsciiDoc files
4. User-Friendly Initialization
The aditi init
command handles all setup complexity:
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
# Pull Vale image
task = progress.add_task("Pulling Vale container image...", total=None)
vale.ensure_image_exists()
# Initialize configuration
task = progress.add_task("Creating Vale configuration...", total=None)
vale.init_vale_config(project_root)
Users see clear progress indicators powered by the Rich library, making the container operations feel seamless.
Technical Challenges Overcome
Volume Mounting Strategy
Vale runs inside a container but needs access to local files. The solution involves careful path calculation:
# Mount project root to /docs in container
cmd = [
self.runtime, "run", "--rm",
"-v", f"{abs_project_root}:/docs",
"-w", "/docs",
self.VALE_IMAGE,
"--output", "JSON",
str(rel_target)
]
This ensures Vale can access both the configuration and the files to lint.
Error Handling Philosophy
Rather than hiding container complexity, the implementation provides clear, actionable error messages:
- No container runtime? Installation instructions
- Network issues? Retry suggestions
- Permission problems? Rootless setup guidance
Testing Strategy
Two test scripts validate the implementation:
- Basic Integration Test: Verifies container runtime, image pulling, and Vale execution
- AsciiDocDITA Rules Test: Confirms style downloading and rule application
Both tests pass, confirming the implementation works with both Podman and Docker.
Benefits of This Approach
- No Custom Images: Using official images reduces maintenance burden
- Automatic Updates: Vale’s package system handles AsciiDocDITA updates
- Security by Default: Podman preference enables rootless operation
- Clear Separation: Vale container handling is isolated in its own module
- User Experience: Simple commands hide complex container operations
Next Steps
With container integration complete, the project can move forward to Phase 1: implementing the rule engine that will process Vale’s output and apply fixes to AsciiDoc files.
The foundation is solid, the tests are passing, and users will have a seamless experience running Vale with AsciiDocDITA rules. Sometimes the best solution isn’t the most complex one—it’s the one that leverages existing tools effectively.
Technical Details
For those interested in the implementation details, the complete solution includes:
ValeContainer
class for managing container lifecycle- Automatic runtime detection (Podman/Docker)
- JSON output parsing for programmatic processing
- Comprehensive error handling and user guidance
- Integration with Typer and Rich for modern CLI experience
The full implementation is available in the Aditi repository, demonstrating how containerized tools can be integrated seamlessly into Python CLI applications.