Docker Usage

Lotio provides Docker images for easy deployment and consistent rendering across different environments:

Quick Start

Best Practice: Use --output-format mov for direct video encoding:

docker pull matrunchyk/lotio:latest

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--output-format mov \

--output video.mov \

--fps 30

This provides direct video encoding with built-in support. See Examples for more options.

Pulling from Docker Hub

Pre-built Docker images are available on Docker Hub:

docker pull matrunchyk/lotio:latest

docker pull matrunchyk/lotio:v1.2.3

The images are automatically built and pushed for each release. Available tags:
- latest - Always points to the most recent release (multi-platform manifest)
- v1.2.3 - Specific version tag (multi-platform manifest)
- v1.2.3-arm64 - Architecture-specific tag for ARM64
- v1.2.3-amd64 - Architecture-specific tag for x86_64

Multi-platform support: The image supports linux/arm64 and linux/amd64. Docker automatically selects the correct platform.

Building the Images

Build Chain

The Docker images use a multi-stage build chain for optimized builds:

Dockerfile.skia → matrunchyk/skia:latest

Dockerfile.lotio → matrunchyk/lotio:latest

Building from Source

Build lotio image:

docker buildx build \

--platform linux/arm64,linux/amd64 \

-t matrunchyk/lotio:test \

-f Dockerfile.lotio \

--build-arg SKIA_IMAGE=matrunchyk/skia:latest \

--push .

Note: The lotio image uses matrunchyk/skia:latest as base (built separately using build_skia_docker_multi.sh).

Image Details

matrunchyk/lotio:latest:
- Base Image: matrunchyk/skia:latest (contains pre-built Skia)
- Architecture: Multi-platform (linux/arm64, linux/amd64)
- Includes:
- lotio binary (/usr/local/bin/lotio)
- lotio static library (/usr/local/lib/liblotio.a)
- lotio headers (/usr/local/include/lotio/)
- Skia libraries and headers (from base image)
- Built-in video encoding support (libavcodec/libavformat)
- Runtime dependencies

Basic Usage

Render Animation to Video (Recommended - Best Performance)

Best Practice: Use --output-format mov for direct video encoding:

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--output-format mov \

--data data.json --fps 30 \

--output video.mov

This combination provides:
- Fastest encoding (up to 15x faster than PNG mode, 65% faster than ProRes 4444 10-bit)
- Smallest file sizes (75% smaller than ProRes)
- Automatic dimension extraction from input JSON
- Full alpha channel support
- Direct encoding using built-in libavcodec/libavformat (no external FFmpeg binary needed)

With Input Directory

docker run --rm \

-v "$(pwd)/samples:/workspace/input:ro" \

-v "$(pwd):/workspace/output" \

matrunchyk/lotio:latest \

--data /workspace/input/data.json --fps 30 --output /workspace/output/video.mov

Command-Line Arguments

Lotio Arguments

All standard lotio arguments are supported:

Note: Frames are output as PNG by default. Use --output-format to encode video directly.

Text Padding

The --text-padding option controls how much of the target text box width is used for text sizing. A value of 0.97 means 97% of the target width is used, leaving 3% padding (1.5% per side).

Text Measurement Mode

The --text-measurement-mode option controls the accuracy vs performance trade-off:

Examples

Direct Video Encoding (No FFmpeg Binary Required)

New in latest version: Use --output-format to encode video directly without requiring the FFmpeg binary:

# MOV format with QTRLE codec (fast, preserves alpha, widely compatible)

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--output-format mov \

--output video.mov \

--fps 30

# FFV1 format (lossless, good compression, preserves alpha)

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--output-format ffv1 \

--output video.mkv \

--fps 30

# Raw RGBA format (fastest, no encoding, largest files, preserves alpha)

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--output-format raw \

--output video.rgb \

--fps 30

Benefits:
- No FFmpeg binary dependency (uses libavcodec/libavformat libraries)
- All formats preserve alpha channel for transparency
- Faster than piping to FFmpeg (no inter-process communication overhead)
- Perfect for stage 2 processing (compositing with other videos)

Format comparison:
- mov (QTRLE): Fast encoding, medium file size, widely compatible
- ffv1: Medium encoding speed, good compression, lossless
- raw: Fastest (zero encoding), largest files, perfect for further processing

Recommended: Best Performance (qtrle + Raw RGBA)

This is the recommended approach for best performance:

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--output-format mov \

--output video.mov \

--fps 30

Why this is best:
- Direct encoding (bypasses ffmpeg binary, fastest)
- Smallest file sizes (QTRLE codec)
- Full alpha channel support
- Automatic dimension extraction from JSON

Basic Video Generation

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json --fps 30 --output-format mov --output video.mov

With Layer Overrides

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--layer-overrides layer-overrides.json \

--fps 30 \

--output-format mov \

--output video.mov

Direct Video Encoding (Maximum Performance)

Recommended: Use --output-format mov for direct encoding (bypasses ffmpeg, fastest).

# Direct encoding (fastest)

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--output-format mov \

--output video.mov \

--fps 30

Performance: Raw RGBA mode is significantly faster than PNG mode, making it ideal for high-volume rendering pipelines.

Dimensions: The entrypoint script automatically extracts dimensions from the input JSON file (using jq to read w and h fields, with grep as fallback). Dimensions are required for raw RGBA mode - if they cannot be extracted from JSON, the script will exit with an error.

With Custom Codec Selection

# MOV format with QTRLE codec (default, fastest)

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--output-format mov \

--data data.json \

--fps 30 \

--output video.mov

With Custom Text Padding and Measurement Mode

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--layer-overrides layer-overrides.json \

--text-padding 0.95 \

--text-measurement-mode pixel-perfect \

--fps 30 \

--output-format mov \

--output video.mov

With Debug Output

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json \

--debug \

--fps 30 \

--output-format mov \

--output video.mov

Using PNG Output Format

If you want to render frames as PNG files:

# Render frames to disk

docker run --rm \

-v "$(pwd):/workspace" \

matrunchyk/lotio:latest \

--data data.json --fps 30 frames/

Output Format

The lotio image supports multiple output formats with alpha channel support. The default video codec is qtrle (QuickTime RLE) for maximum speed.

Video Specifications

Codec Details

QuickTime RLE (qtrle) - Default:
- Pixel Format: argb
- Fastest encoding (65% faster than ProRes 4444 10-bit)
- Smallest file sizes (75% smaller than ProRes)
- Best for simple graphics/text content
- May have playback issues in some players

ProRes 4444 (8-bit):
- Pixel Format: yuva444p
- Balanced option, QuickTime compatible
- Smooth playback

ProRes 4444 (10-bit):
- Pixel Format: yuva444p10le
- Highest quality, slower encoding
- Widely compatible with video editing software

Volume Mounts

Input Files

Mount your Lottie JSON files and any dependencies:

-v "$(pwd)/samples:/workspace/input:ro"

Output Directory

Mount a directory for output:

-v "$(pwd):/workspace/output"

The output video will be written to the path specified by --output.

Layer Overrides

Layer overrides work the same as the CLI. Mount your layer overrides file and reference it:

docker run --rm \

-v "$(pwd)/samples:/workspace/input:ro" \

-v "$(pwd):/workspace/output" \

matrunchyk/lotio:latest \

--data /workspace/input/data.json \

--layer-overrides /workspace/input/layer-overrides.json \

--fps 30 \

--output /workspace/output/video.mov

Example layer-overrides.json:

{

"textLayers": {

"Text_1": {

"minSize": 50,

"maxSize": 222,

"fallbackText": "Default text",

"textBoxWidth": 720,

"value": "Custom text value"

},

"Text_2": {

"value": "Another text"

}

},

"imageLayers": {

"image_0": {

"filePath": "images/",

"fileName": "logo.png"

},

"image_1": {

"filePath": "/workspace/input/",

"fileName": "bg.png"

}

}

}

Image Layers in Layer Overrides

Path Resolution:
- Absolute paths: Used as-is (e.g., /workspace/input/images/logo.png)
- Relative paths: Resolved relative to the layer-overrides.json file's directory (NOT the current working directory)
- Example: If layer-overrides.json is at /workspace/input/layer-overrides.json and filePath is "images/", it resolves relative to /workspace/input/
- Important: This is different from input.json and --layer-overrides paths, which are resolved relative to the current working directory
- URLs are NOT supported: HTTP (http://) and HTTPS (https://) URLs are not supported
- Empty filePath: If filePath is an empty string, fileName must contain the full path

Notes:
- Image paths in the original Lottie JSON (data.json) are resolved relative to data.json's parent directory
- If an asset ID is not in imageLayers, the original u and p from data.json are used
- Both filePath and fileName are optional - if not specified, defaults from assets[].u and assets[].p are used

Fonts

Fonts can be provided in two ways:

  1. --fonts <dir> — Mount a directory and pass it to lotio. Fonts are looked up in this directory first, then in the JSON directory and its fonts/ subdirectory.

docker run --rm \

-v "$(pwd)/myfonts:/workspace/fonts:ro" \

-v "$(pwd)/animation.json:/workspace/input.json:ro" \

-v "$(pwd):/workspace/output" \

matrunchyk/lotio:latest \

--data /workspace/input.json --fonts /workspace/fonts --fps 30 --output /workspace/output/video.mov

  1. System fonts (fontconfig) — Mount fonts into a system font directory so fontconfig can find them:

docker run --rm \

-v "$(pwd)/fonts:/usr/local/share/fonts:ro" \

-v "$(pwd)/animation.json:/workspace/input.json:ro" \

-v "$(pwd):/workspace/output" \

matrunchyk/lotio:latest \

--data /workspace/input.json --fps 30 --output /workspace/output/video.mov

Fonts should be in TTF or OTF format. With --fonts, lotio looks in that directory by name (e.g. OpenSans-Bold.ttf); with fontconfig, fonts are discovered by fontconfig.

Environment Variables

The container sets up the following environment variables:

Troubleshooting

Video Not Generated

Check that:
- Input file path is correct and mounted
- Relative paths are resolved relative to the current working directory (cwd) inside the container
- Use absolute paths (e.g., /workspace/input/data.json) for clarity
- Output directory is writable
- Sufficient disk space is available
- Image paths in imageLayers are correct
- Relative paths in imageLayers.filePath are resolved relative to the layer-overrides.json file's directory (NOT the current working directory)

Text Not Replacing

Verify:
- Layer overrides file is mounted and accessible (supports both absolute and relative paths)
- Layer names in layer-overrides.json match layer names in Lottie JSON
- Layer overrides file is valid JSON

Image Path Issues

If images aren't loading:
- Verify image paths in imageLayers are correct
- Relative paths in imageLayers.filePath are resolved relative to the layer-overrides.json file's directory (NOT the current working directory)
- Example: If layer-overrides.json is at /workspace/config/overrides.json and imageLayers has "image_0": { "filePath": "images/", "fileName": "logo.png" }, it resolves to /workspace/config/images/logo.png
- Absolute paths in imageLayers.filePath are used as-is
- URLs (http://, https://) and data URIs (data:) are NOT supported in imageLayers
- Ensure image files exist and are accessible from within the container

Font Issues

If fonts aren't loading:
- Use --fonts <dir> to point to a mounted directory containing the required .ttf files (e.g. --fonts /workspace/fonts), or mount fonts into a system font path for fontconfig
- Ensure the fonts directory is mounted and readable
- Check font file format (TTF/OTF)
- Verify font names match those in Lottie JSON (e.g. OpenSans-BoldOpenSans-Bold.ttf)

Debug Mode

Enable debug output to see detailed information:

docker run --rm \

-v "$(pwd)/animation.json:/workspace/input.json:ro" \

-v "$(pwd):/workspace/output" \

matrunchyk/lotio:latest \

--data /workspace/input.json --debug --fps 30 --output /workspace/output/video.mov

Performance

Optimization Features

The Docker container includes several performance optimizations:

  1. Direct Video Encoding (--output-format mov|ffv1|raw): Fastest option, bypasses ffmpeg binary
  2. Direct encoding within lotio using libavcodec/libavformat
  3. Eliminates inter-process communication overhead
  4. Best for high-volume rendering pipelines

  5. Optimized Codec Selection: Default qtrle codec for maximum speed

  6. Fastest encoding (up to 65% faster than ProRes 4444 10-bit)
  7. Smallest file sizes (75% smaller than ProRes)
  8. Best for simple graphics/text content

  9. Constant Frame Rate (CFR): Ensures smooth playback and faster encoding

  10. Explicit frame rate setting
  11. Optimized timescale (30000)
  12. Better frame timing precision

Performance Comparison

Mode Encoding Time File Size Use Case
PNG + ProRes 4444 10-bit ~13s 76M Baseline (original)
Raw RGBA + ProRes 4444 8-bit ~1.86s 76M Balanced (24% faster)
Raw RGBA + qtrle ~0.85s 19M Fastest (65% faster, 75% smaller)

Total improvement: ~15x faster than original implementation.

Container Performance

The Docker container uses:
- Multi-threaded rendering (one thread per CPU core)
- Optimized Skia build with official release settings
- Hardware-accelerated encoding where available

For best performance:
- Use --output-format mov for maximum speed (direct encoding)
- Use SSD storage for input/output
- Allocate sufficient CPU cores to the container
- Use local volume mounts (not network filesystems)

CI/CD Integration

The Docker image can be used in CI/CD pipelines:

- name: Render animation

run: |

docker pull matrunchyk/lotio:latest

docker run --rm \

-v "${{ github.workspace }}/animation.json:/workspace/input.json:ro" \

-v "${{ github.workspace }}/output:/workspace/output" \

matrunchyk/lotio:latest \

--data /workspace/input.json --fps 30 \

--output /workspace/output/video.mov

Build Optimizations

Docker Build Chain

The build chain is optimized for speed:

  1. Dockerfile.skia - Builds Skia once (takes 15-20 minutes, but cached)
  2. Dockerfile.lotio - Uses pre-built Skia + builds lotio with built-in video encoding (takes 2-3 minutes)

Total build time: ~20-30 minutes (first time), but subsequent builds are much faster due to caching.

Lambda/Container Integration

When using the lotio image in Lambda functions or containers, you can use lotio directly with built-in video encoding support:

Command Format

lotio [LOTIO_OPTIONS] --data <input.json> [--fps <fps>] --output-format <format> --output <video.mov>

Key Points

  1. Direct video encoding: Use --output-format mov|ffv1|raw for direct encoding (no external FFmpeg binary needed)
  2. All lotio options supported: --layer-overrides, --fonts, --text-padding, --text-measurement-mode, --debug all work
  3. Dimension extraction: Dimensions are automatically extracted from JSON (using jq or grep fallback)

Complete Example

lotio \

--layer-overrides /tmp/overrides.json \

--text-padding 0.95 \

--text-measurement-mode pixel-perfect \

--data /tmp/input.json \

--fps 30 \

--output-format mov \

--output /tmp/output.mov

TypeScript/Node.js Integration Example

import { exec } from "child_process";

import { promisify } from "util";

const execAsync = promisify(exec);

// Build command arguments

const args = [

"--data", inputJsonPath,

"--output-format", "mov",

"--output", outputVideoPath

];

// Add optional arguments

if (layerOverridesPath) {

args.push("--layer-overrides", layerOverridesPath);

}

if (textPadding !== undefined) {

args.push("--text-padding", textPadding.toString());

}

if (textMeasurementMode) {

args.push("--text-measurement-mode", textMeasurementMode);

}

if (fps !== undefined) {

args.push("--fps", fps.toString());

}

// Execute

await execAsync(`lotio ${args.map(a => `"${a}"`).join(" ")}`);

Limitations

See Also