πŸš€ DeploymentΒΆ

TINO ships as a single Docker image with no external dependencies β€” just point it at an OIDC provider and add a data volume, and you’re up.

πŸ–₯️ System requirementsΒΆ

TINO is distributed as a multi-architecture Docker image and runs comfortably on modest hardware.

The official ghcr.io/confirm/tino image is published for the following platforms:

  • linux/amd64 β€” 64-bit x86 (Intel/AMD)

  • linux/arm64 β€” 64-bit ARM (AArch64, including Apple Silicon)

No other architectures are supported.

TINO needs at least 128 MB of memory, but 256 MB is recommended. Typst compilation and active collaboration rooms add to that, so allow more headroom for large documents or many concurrent editors.

Note

32-bit ARM (linux/arm/v7) and RISC-V (linux/riscv64) are not supported.

Several of TINO’s compiled Python dependencies publish no prebuilt wheels for these platforms, and the runtime image intentionally ships without a build toolchain:

  • linux/riscv64 β€” pycrdt (the Rust library powering real-time collaboration) has no RISC-V wheel.

  • linux/arm/v7 β€” uvloop and httptools (Uvicorn’s accelerated event loop and HTTP parser) have no 32-bit ARM wheels.

⚑ Quick start (demo)¢

To try TINO without setting up an OIDC provider, disable authentication:

docker run -d \
    --name tino \
    -e TINO_AUTH_DISABLED=true \
    -p 5000:5000 \
    -v data:/data \
    ghcr.io/confirm/tino

See also

See Disabling authentication for more information.

🐳 Docker¢

Docker imageΒΆ

To deploy TINO, use the following Docker image:

ghcr.io/confirm/tino

See also

Check the Git tags for explicit Docker image versions.

Docker commandΒΆ

To deploy TINO via a simple docker command, use the following CLI arguments:

docker run -d \
    --name tino \
    -e TINO_BASE_URL=https://tino.example.com \
    -e TINO_OIDC_DISCOVERY_URL=https://sso.example.com/.well-known/openid-configuration \
    -e TINO_OIDC_CLIENT_SECRET=change-me \
    -p 5000:5000 \
    -v data:/data \
    ghcr.io/confirm/tino

Hint

It’s recommended to deploy TINO via Docker Compose and use the additional deployment settings mentioned in the Compose file.

Docker ComposeΒΆ

Use the following docker-compose.yml Compose file to start TINO:

---
#
# Recommended Docker Compose deployment for TINO.
#
# This file is verbose and heavily commented by design. TINO would run with
# fewer options, but that is not recommended.
#
# Copy this file, adjust the values below, then run:
#
#   docker compose up -d
#

services:

  tino:

    #
    # 🐳 Docker image
    #
    # Pin a release tag (e.g. ghcr.io/confirm/tino:1.2.3) for reproducible deployments.
    #
    # Find tags right here:
    # https://github.com/confirm/tino/pkgs/container/tino
    #

    image: ghcr.io/confirm/tino

    #
    # 🌍 Environment
    #
    # TINO is configured via environment variables.
    #
    # See the configuration reference for the full list of TINO_* environment variables:
    # https://docs.tinotype.com/operations/configuration.html
    #

    environment:

      # Public URL TINO is served on (used for OIDC redirects and, if enabled, MCP).
      TINO_BASE_URL: https://tino.example.com

      # Your OIDC provider's discovery endpoint.
      TINO_OIDC_DISCOVERY_URL: https://sso.example.com/.well-known/openid-configuration

      # OIDC client secret β€” replace this.
      #
      # ⚠️ Keep secrets out of version control, inject it instead. E.g.:
      #
      #   - Via an .env file
      #   - Via a secrets manager
      #   - Via CI variables
      #
      TINO_OIDC_CLIENT_SECRET: change-me

    #
    # πŸ”— Network
    #
    # TINO listens on port 5000 inside the container, which is mapped from the
    # host to the container.
    #
    # ⚠️ Typically a TLS-terminating reverse proxy sits in front of it.
    #
    # When it does, do one of the following:
    #
    #   - Either bind to localhost ('127.0.0.1:5000:5000'),
    #   - or drop the port mapping and share an internal network.
    #

    ports:
      - '5000:5000'

    #
    # πŸš€ Deployment
    #
    # The deployment resource constraints are optional, but it's recommended as
    # they limit the resources TINO can use.
    #
    # Feel free to adapt them to your requirements.
    #
    # For the system requriements, check out:
    # https://docs.tinotype.com/operations/deployment.html#system-requirements
    #

    deploy:
      resources:
        limits:          # hard ceiling enforced by the runtime
          cpus: '1'
          memory: '256M'
        reservations:    # minimum guaranteed for scheduling
          cpus: '0.1'
          memory: '128M'

    #
    # πŸ”’ Security
    #
    # TINO runs without defining the security-related options below.
    #
    # However, it's recommended to use them, as they harden the container,
    # resp. runtime environemnt in case anything goes wrong.
    #

    # Prevent the process and its children from gaining new privileges
    # (e.g. via setuid binaries).
    security_opt:
      - no-new-privileges:true

    # Drop all Linux capabilities β€” TINO runs as a non-root user and binds a
    # high port (5000), so it needs none of them.
    cap_drop:
      - ALL

    # Run with a read-only root filesystem...
    read_only: true

    #
    # πŸ“ Filesystem
    #
    # TINO requires 2 writable directories:
    #
    #   1. The data directory for the persisted user data (buckets, fonts and local packages).
    #   2. The tmp directory (Typst cache, Gunicorn worker files).
    #

    # Add tmpfs (required when `read_only: true` is set).
    tmpfs:
      - /tmp

    # Persisten data volume.
    volumes:
      - data:/data

    #
    # πŸ”„ Restart
    #

    # Restart the container automatically unless it was explicitly stopped.
    restart: unless-stopped


#
# πŸ’Ύ Volume
#
# Named volume backing TINO_DATA_DIR (mounted at /data in the container).
#
# ⚠️ Don't forget to backup this volume!
#

volumes:
  data:

Then bring the stack up with:

docker compose up -d

πŸ“¦ Standalone (without Docker)ΒΆ

Warning

Running TINO without Docker is not recommended and unsupported. You are responsible for managing Python, the Typst CLI, process supervision, and upgrades yourself. The Docker image is the only officially supported deployment method.

If you cannot use Docker, TINO can be installed as a regular Python package and run with Gunicorn.

PrerequisitesΒΆ

Building the wheelΒΆ

make package

This produces a wheel in build/ (e.g. build/tino-<version>-py3-none-any.whl).

InstallingΒΆ

pip install build/tino-*.whl

This installs TINO and all its Python dependencies (including Gunicorn and Uvicorn).

RunningΒΆ

export TINO_BASE_URL=https://tino.example.com
export TINO_OIDC_DISCOVERY_URL=https://sso.example.com/.well-known/openid-configuration
export TINO_OIDC_CLIENT_SECRET=change-me
# … set any other TINO_* variables (see Configuration)

gunicorn \
    -k uvicorn.workers.UvicornWorker \
    -w 1 \
    -b 0.0.0.0:5000 \
    'tino:create_app()'

This is the same command the Docker image runs internally.

Hint

See 🎚 Configuration for the full list of environment variables. Make sure TINO_DATA_DIR points to a writable directory that is backed up regularly.

πŸ”— IntegrationΒΆ

Reverse proxyΒΆ

TINO is typically deployed behind a reverse proxy (e.g. nginx, Traefik, Caddy) that terminates TLS.

Set TINO_BASE_URL to the public https:// URL the proxy serves TINO on. It is used to build OIDC redirect URLs and, when MCP is enabled, as the resource identifier in the OAuth discovery metadata.

OIDCΒΆ

TINO requires an OAuth 2.0 / OpenID Connect (OIDC) provider for authentication. Any provider that supports OpenID Connect Discovery is supported (e.g. Keycloak, Authentik, Azure AD, Okta, Zitadel).

Register a new client (application) with your OIDC provider:

  1. Set the client ID to TINO_OIDC_CLIENT_ID, or vice-versa

  2. Set the redirect URI (callback URL) to:

    https://tino.example.com/oidc/callback
    
  3. Set the post-logout redirect URI to:

    https://tino.example.com/login
    
  4. Ensure the following scopes are enabled: openid, email, profile.

  5. Make sure the ID token includes a claim with the user’s group memberships (see TINO_OIDC_GROUPS_CLAIM).

  6. Make sure a user matches an admin group (see TINO_ADMIN_GROUPS)

  7. Set the TINO_OIDC_CLIENT_SECRET to the client secret

See also

See 🎚 Configuration for the full list of environment variables.

MCPΒΆ

The MCP server is disabled by default. To enable it, set TINO_MCP_ENABLED to true.

MCP authentication happens via OAuth 2.0, and the OIDC provider must support CIMD. No additional client registration is needed.

Note

On Keycloak this requires version 26.6.0 or later, where Client ID Metadata Document support ships as an experimental feature. Check out the Client Registration docs for more information.

See also

Check out the MCP authentication for how the auth flow works, and the MCP server documentation for usage and settings.

Disabling authenticationΒΆ

For local development, demo environments or external authentication, you can disable the built-in OIDC authentication entirely by setting the TINO_AUTH_DISABLED environment variable:

TINO_AUTH_DISABLED=true

Warning

When authentication is disabled, all requests are treated as a built-in no-auth user with full administrator privileges. OIDC configuration is not required in this mode.

In production this is only safe when TINO sits behind a reverse proxy or gateway that already handles authentication (e.g. OAuth2 Proxy, Authelia, or a cloud IAP).