docker
This guide explains how to use Lintro with Docker for containerized development and CI environments.
Docker Usage Guide
This guide explains how to use Lintro with Docker for containerized development and CI environments.
Quick Start
Using Published Image (Recommended)
The easiest way to use Lintro is with the pre-built image from GitHub Container Registry:
# Basic usage (tools image - includes all external tools)
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:latest check
# With grid formatting
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:latest check --output-format grid
# Run specific tools
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:latest check --tools ruff,prettier
# Format code
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:latest format
# Base image (minimal, no external tools)
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro-base:latest check
Development Setup
# Clone the repository
git clone https://github.com/lgtm-hq/py-lintro.git
cd py-lintro
# Make the Docker script executable
chmod +x scripts/**/*.sh
# Run Lintro with Docker
./scripts/docker/docker-lintro.sh check --output-format grid
Basic Commands
# Check code for issues
./scripts/docker/docker-lintro.sh check
# Auto-fix issues where possible
./scripts/docker/docker-lintro.sh format
# Use grid formatting (recommended)
./scripts/docker/docker-lintro.sh check --output-format grid --group-by code
# List available tools
./scripts/docker/docker-lintro.sh list-tools
Published Docker Image
Lintro provides a pre-built Docker image available on GitHub Container Registry (GHCR):
Image Details
The tools-backed image (recommended for CI) is published as
ghcr.io/lgtm-hq/py-lintro.
The minimal image (lintro only; supply linters from the host or opt-in installs) is
ghcr.io/lgtm-hq/py-lintro-base. A separate package avoids digest/tag collisions so
pins to py-lintro never resolve to the minimal variant.
Full image (ghcr.io/lgtm-hq/py-lintro) tags:
latest— default branch release build with bundled external toolsmain— main branch versionv1.0.0— semver releases when published
Base image (ghcr.io/lgtm-hq/py-lintro-base) tags mirror the same semver and
sha-* conventions without a -base suffix on the tag (the package name carries that
distinction).
Migration: Older docs referred to ghcr.io/lgtm-hq/py-lintro:base or
py-lintro:vX.Y.Z-base. Use ghcr.io/lgtm-hq/py-lintro-base:latest or
py-lintro-base:vX.Y.Z instead.
Tools Base Image (lintro-tools)
The tools base image is published as ghcr.io/lgtm-hq/lintro-tools. It contains
the full external toolchain lintro drives — Rust toolchain (clippy, rustfmt,
cargo-audit, cargo-deny), bun/Node tools (prettier, oxlint, tsc, stylelint, …), and
standalone binaries (hadolint, shellcheck, shfmt, gitleaks, vale, …) — but not an
installed lintro application (no virtualenv or entrypoint; lintro source is present
under /app only because the installer reads its tool-version manifest from it). After
the planned digest-pinned FROM flip, the py-lintro full image will be built on top
of it; you can also use it directly as a base for your own images when you want the
pre-built linter toolchain without lintro:
# Pin by digest for reproducible builds (recommended)
FROM ghcr.io/lgtm-hq/lintro-tools:latest@sha256:<digest>
Details:
- Built from
docker/tools.Dockerfilein the repository root; tool versions are pinned inlintro/_tool_versions.py. - Rebuilt when the tools Dockerfile or pinned tool versions change, plus a weekly no-cache rebuild so tool binaries and the OS layer pick up fresh CVE patches.
- Signed with Sigstore Cosign (keyless) and published with SBOM and build provenance attestations.
- Tags:
latest,main, and immutablesha-<commit>tags. Pin by digest and let Renovate’s native Docker digest support manage bumps (this repository does exactly that for the rootDockerfile).
Using the Published Image
# Pull the latest image
docker pull ghcr.io/lgtm-hq/py-lintro:latest
# Run with the published image (tools image)
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:latest check
# Use a specific version
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:main check
# Use the base image (minimal)
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro-base:latest check
CI/CD Integration
Use the published image in your CI/CD pipelines:
# GitHub Actions example
- name: Run Lintro
run: |
docker run --rm -v ${{ github.workspace }}:/code \
ghcr.io/lgtm-hq/py-lintro:latest check --output-format grid
# GitLab CI example
lintro:
image: ghcr.io/lgtm-hq/py-lintro:latest
script:
- lintro check --output-format grid
# Base image example (minimal, no external tools)
lintro_base:
image: ghcr.io/lgtm-hq/py-lintro-base:latest
script:
- lintro check --output-format grid
Container Auto-Detection
Lintro automatically detects when it is running inside a container (Docker, Podman, LXC, Kubernetes, etc.) and adjusts its behavior accordingly:
- Auto-install defaults to enabled in container environments, so Node.js
dependencies are installed automatically without requiring
--auto-installor configuration changes. - Non-interactive mode is assumed — confirmation prompts are skipped automatically.
- A pre-execution summary is displayed before tools run, showing the detected environment (“Container”), which tools will run, and any tools that were skipped (with reasons).
Detection checks (in order):
/.dockerenvfile exists (Docker)/run/.containerenvfile exists (Podman)CONTAINERenvironment variable is set/proc/1/cgroupcontainsdocker,lxc,containerd, orkubepods
This means no extra configuration is needed when running the Docker image — it works out of the box.
Node.js Dependency Auto-Install
When using Docker, Lintro automatically installs Node.js dependencies because container environments enable auto-install by default. This happens when:
- A
package.jsonfile exists in the mounted/codedirectory - The
node_modulesdirectory is missing or empty
How it works:
- Lintro detects the container environment and enables auto-install
- If
node_modulesis missing, it runsbun install --frozen-lockfile(falls back to regularbun installif lockfile fails) - If
bunis unavailable, it usesnpm ci(falls back tonpm install) - This ensures tools like
tsc,oxlint,prettier, andmarkdownlint-cli2work immediately
Example:
# Mount a TypeScript project - dependencies are auto-installed in Docker
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:latest check --tools tsc
Controlling auto-install:
You can explicitly control auto-install behavior using environment variables:
# Disable auto-install even in Docker (override container detection)
docker run --rm -e LINTRO_AUTO_INSTALL_DEPS=0 -v $(pwd):/code \
ghcr.io/lgtm-hq/py-lintro:latest check --tools tsc
# Or use the --yes flag to skip confirmation prompts for auto-install
docker run --rm -v $(pwd):/code ghcr.io/lgtm-hq/py-lintro:latest check --tools tsc --yes
Per-tool auto-install can also be configured in .lintro-config.yaml:
tools:
tsc:
auto_install: true # Enable auto-install for tsc specifically
prettier:
auto_install: false # Disable auto-install for prettier
Note: For local usage (outside Docker), use the --auto-install flag, set
auto_install_deps: true in your configuration file, or use per-tool auto_install
settings. See Configuration for details.
Building the Image Locally
# Build the Docker image
docker build -t lintro:latest .
# Or use docker compose
docker compose build
Running Commands
Using the Shell Script (Recommended)
The scripts/docker/docker-lintro.sh script provides the easiest way to run Lintro in
Docker:
# Basic usage
./scripts/docker/docker-lintro.sh check --output-format grid
# Specific tools
./scripts/docker/docker-lintro.sh check --tools ruff,prettier
# Format code
./scripts/docker/docker-lintro.sh fmt --tools ruff
# Export results
./scripts/docker/docker-lintro.sh check --output-format grid --output results.txt
Using Docker Directly
# Basic check
docker run --rm -v "$(pwd):/code" lintro:latest check
# With grid formatting
docker run --rm -v "$(pwd):/code" lintro:latest check --output-format grid
# Format code
docker run --rm -v "$(pwd):/code" lintro:latest fmt --tools ruff
Using Docker Compose
# Check code
docker compose run --rm lintro check
# Format code
docker compose run --rm lintro format --tools ruff
# Specific tools
docker compose run --rm lintro check --tools ruff,prettier
Command Options
Check Command
./scripts/docker/docker-lintro.sh check [OPTIONS] [PATHS]...
Options:
--tools TEXT- Comma-separated list of tools (default: all)--output-format grid- Format output as a grid table--group-by [file|code|none|auto]- How to group issues--output FILE- Save output to file--exclude TEXT- Patterns to exclude--include-venv- Include virtual environment directories
Tool-specific options:
--tool-options TEXT- Tool-specific options in format tool:option=value
Format Command
./scripts/docker/docker-lintro.sh fmt [OPTIONS] [PATHS]...
Same options as check command, but only runs tools that can auto-fix issues.
List Tools Command
./scripts/docker/docker-lintro.sh list-tools [OPTIONS]
Options:
--show-conflicts- Show potential conflicts between tools--output FILE- Save tool list to file
Output to Files
When using the --output option, files are created in your current directory:
# Save check results
./scripts/docker/docker-lintro.sh check --output-format grid --output results.txt
# Save to subdirectory (make sure it exists)
./scripts/docker/docker-lintro.sh check --output-format grid --output reports/results.txt
# Save tool list
./scripts/docker/docker-lintro.sh list-tools --output tools.txt
Common Use Cases
Code Quality Checks
# Basic quality check
./scripts/docker/docker-lintro.sh check --output-format grid
# Group by error type for easier fixing
./scripts/docker/docker-lintro.sh check --output-format grid --group-by code
# Check specific files or directories
./scripts/docker/docker-lintro.sh check src/ tests/ --output-format grid
# Use only specific tools
./scripts/docker/docker-lintro.sh check --tools ruff,pydoclint --output-format grid
Code Formatting
# Format with all available tools
./scripts/docker/docker-lintro.sh fmt
# Format with specific tools
./scripts/docker/docker-lintro.sh fmt --tools ruff,prettier
# Format specific directories
./scripts/docker/docker-lintro.sh fmt src/ --tools ruff
Docker CI/CD Integration
# CI-friendly output (no grid formatting)
./scripts/docker/docker-lintro.sh check --output ci-results.txt
# Exit with error code if issues found
./scripts/docker/docker-lintro.sh check && echo "No issues found" || echo "Issues detected"
Testing
Run Tests in Docker
# Run all integration tests (including Docker-only tests)
./docker-test.sh
# Run local tests only
./run-tests.sh
Development Workflow
# Check your changes
./scripts/docker/docker-lintro.sh check --output-format grid
# Fix auto-fixable issues
./scripts/docker/docker-lintro.sh fmt
# Run tests
./docker-test.sh
# Check again to ensure everything is clean
./scripts/docker/docker-lintro.sh check --output-format grid
Volume Permissions
Lintro’s Docker image automatically handles volume permission mismatches. When the
container starts as root (the default), the entrypoint detects the UID/GID that owns the
mounted /code directory and re-executes as that user via
gosu. This means:
docker run --rm -v "$(pwd):/code" lintro checkjust works — no--userflag needed- The container process runs as the same UID that owns your project files
bun install(auto-install) can writenode_modulesinto the project directory- No files on the host have their ownership changed
How it works
- Container starts as root (entrypoint runs as PID 1)
- Entrypoint reads the volume owner’s UID and GID separately (
stat -c '%u' /codeandstat -c '%g' /code) - If the detected UID/GID differs from the current user, the entrypoint re-execs itself
as that UID:GID via
gosu - HOME, CARGO_HOME, and BUN_INSTALL are redirected to
/tmp(the mapped UID won’t have a home directory inside the container) - Lintro runs as the matched UID:GID — full read/write access to
/code
Restricted environments
In some environments, the UID auto-detection cannot be used:
| Environment | Why | Workaround |
|---|---|---|
Kubernetes with runAsNonRoot: true | Pod is rejected before entrypoint runs | Set securityContext.runAsUser to match the volume owner and use --user |
Read-only mounts (-v ...:/code:ro) | Correct UID but writes still fail | Pre-install node_modules or remove :ro |
| Rootless Docker / Podman | Usually works, but UID namespace remapping varies | Use --user "$(id -u):$(id -g)" if auto-detection fails |
When using --user explicitly, Lintro detects the non-root context and automatically
redirects HOME, CARGO_HOME, and BUN_INSTALL to writable locations (/tmp).
Troubleshooting
Permission Issues
Volume permissions are handled automatically (see Volume Permissions above). If you still encounter permission errors:
# Fallback: explicitly pass your user ID
docker run --rm -v "$(pwd):/code" --user "$(id -u):$(id -g)" lintro:latest check
This is typically only needed in restricted environments like Kubernetes with
runAsNonRoot pod security policies.
Volume Mounting Issues
Ensure you’re using the absolute path for volume mounting:
# Use absolute path
docker run --rm -v "$(pwd):/code" lintro:latest check
# Check current directory
pwd
Docker Script Issues
If the scripts/docker/docker-lintro.sh script isn’t working:
- Check permissions:
chmod +x scripts/docker/docker-lintro.sh - Verify Docker is running:
docker --version - Ensure you’re in the correct directory: Should contain
Dockerfile
Build Issues
If Docker build fails:
# Clean build (no cache)
docker build --no-cache -t lintro:latest .
# Check Docker logs
docker build -t lintro:latest . 2>&1 | tee build.log
Advanced Usage
Custom Configuration
Build a custom image with your own configuration:
# Copy your config files to the container
docker build -t lintro:custom .
# Run with custom config
docker run --rm -v "$(pwd):/code" lintro:custom check
Performance Optimization
For large codebases:
# Use specific tools only
./scripts/docker/docker-lintro.sh check --tools ruff --output-format grid
# Exclude unnecessary patterns
./scripts/docker/docker-lintro.sh check --exclude "*.pyc,venv,node_modules" --output-format grid
# Process specific directories
./scripts/docker/docker-lintro.sh check src/ --output-format grid
Integration with Other Tools
Makefile Integration
lint:
./scripts/docker/docker-lintro.sh check --output-format grid
fix:
./scripts/docker/docker-lintro.sh fmt
lint-ci:
./scripts/docker/docker-lintro.sh check --output lint-results.txt
GitHub Actions
- name: Run Lintro
run: |
chmod +x scripts/docker/docker-lintro.sh
./scripts/docker/docker-lintro.sh check --output-format grid --output lintro-results.txt
Skipped Tools and Pre-Execution Summary
When running in Docker, Lintro displays a pre-execution configuration summary before tools run. This table shows:
- Environment: Container (auto-detected) or Local
- Auto-install: Whether auto-install is enabled and why
- Tools: Which tools will run
- Skipped tools: Tools that were skipped with reasons
Tools can be skipped for several reasons:
| Reason | Description |
|---|---|
node_modules not found | Node.js deps missing and auto-install is disabled |
disabled in config | Tool explicitly disabled via tools.<name>.enabled |
not in enabled_tools | Tool not listed in execution.enabled_tools |
deferred to <tool> | Framework-specific tool preferred (e.g., tsc to vue-tsc) |
version check failed | Tool version below minimum required |
Skipped tools appear in the summary table with a SKIP status and a note explaining
why, so you always know what happened.
Example output:
┌─────────────┬────────┬────────┬─────────────────────────┐
│ Tool │ Status │ Issues │ Notes │
├─────────────┼────────┼────────┼─────────────────────────┤
│ ruff │ PASS │ 0 │ │
│ tsc │ SKIP │ - │ deferred to astro-check │
│ astro-check │ PASS │ 0 │ │
└─────────────┴────────┴────────┴─────────────────────────┘
Best Practices
- Use grid formatting for better readability:
--output-format grid - Group by error type for systematic fixing:
--group-by code - Save results to files for CI integration:
--output results.txt - Use specific tools for faster checks:
--tools ruff,prettier - Exclude irrelevant files to reduce noise:
--exclude "venv,node_modules" - Use
--yesto skip confirmation prompts in scripts:lintro check --yes