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

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 tools
  • main — main branch version
  • v1.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.Dockerfile in the repository root; tool versions are pinned in lintro/_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 immutable sha-<commit> tags. Pin by digest and let Renovate’s native Docker digest support manage bumps (this repository does exactly that for the root Dockerfile).

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-install or 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):

  1. /.dockerenv file exists (Docker)
  2. /run/.containerenv file exists (Podman)
  3. CONTAINER environment variable is set
  4. /proc/1/cgroup contains docker, lxc, containerd, or kubepods

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:

  1. A package.json file exists in the mounted /code directory
  2. The node_modules directory is missing or empty

How it works:

  • Lintro detects the container environment and enables auto-install
  • If node_modules is missing, it runs bun install --frozen-lockfile (falls back to regular bun install if lockfile fails)
  • If bun is unavailable, it uses npm ci (falls back to npm install)
  • This ensures tools like tsc, oxlint, prettier, and markdownlint-cli2 work 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

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 check just works — no --user flag needed
  • The container process runs as the same UID that owns your project files
  • bun install (auto-install) can write node_modules into the project directory
  • No files on the host have their ownership changed

How it works

  1. Container starts as root (entrypoint runs as PID 1)
  2. Entrypoint reads the volume owner’s UID and GID separately (stat -c '%u' /code and stat -c '%g' /code)
  3. If the detected UID/GID differs from the current user, the entrypoint re-execs itself as that UID:GID via gosu
  4. HOME, CARGO_HOME, and BUN_INSTALL are redirected to /tmp (the mapped UID won’t have a home directory inside the container)
  5. 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:

EnvironmentWhyWorkaround
Kubernetes with runAsNonRoot: truePod is rejected before entrypoint runsSet securityContext.runAsUser to match the volume owner and use --user
Read-only mounts (-v ...:/code:ro)Correct UID but writes still failPre-install node_modules or remove :ro
Rootless Docker / PodmanUsually works, but UID namespace remapping variesUse --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:

  1. Check permissions: chmod +x scripts/docker/docker-lintro.sh
  2. Verify Docker is running: docker --version
  3. 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:

ReasonDescription
node_modules not foundNode.js deps missing and auto-install is disabled
disabled in configTool explicitly disabled via tools.<name>.enabled
not in enabled_toolsTool not listed in execution.enabled_tools
deferred to <tool>Framework-specific tool preferred (e.g., tsc to vue-tsc)
version check failedTool 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

  1. Use grid formatting for better readability: --output-format grid
  2. Group by error type for systematic fixing: --group-by code
  3. Save results to files for CI integration: --output results.txt
  4. Use specific tools for faster checks: --tools ruff,prettier
  5. Exclude irrelevant files to reduce noise: --exclude "venv,node_modules"
  6. Use --yes to skip confirmation prompts in scripts: lintro check --yes