Author:
    Creation:2026-06-30Last update:2026-06-30

    Self-Hosting Intlayer

    Intlayer can run entirely on your own infrastructure — no Intlayer Cloud account required. A single command boots a production-ready stack:

    sh
    curl -fsSL https://intlayer.org/install.sh | sh

    The installer downloads a docker-compose.yml and a .env, auto-generates the required secrets, and starts all containers with docker compose up -d.

    Table of Contents


    Architecture

    plaintext
                    ┌─────────────────────────────┐ browser ──────▶ │  app  (TanStack Start)  :3000│ ──┐                └─────────────────────────────┘   │ VITE_BACKEND_URL                ┌─────────────────────────────┐   │                │  backend (Fastify/Bun)  :3100│ ◀─┘                └──────────────┬──────────────┘          ┌──────────┬─────────┼──────────┬───────────┐          ▼          ▼         ▼          ▼           ▼     mongo:27017  redis:6379  minio:9000  mailpit:1025  Chromium     (1-node RS)             (S3 API)     (SMTP)        (in-image)                             minio:9001   mailpit:8025                             (console)    (web UI)

    Chromium (used for Puppeteer screenshot generation) is bundled inside the backend image — no separate container is needed.


    Prerequisites

    • Docker ≥ 24 and Docker Compose ≥ v2. If either is missing, the installer prints the install link and exits.
    • Ports 3000, 3100, 8025, 9000, and 9001 available on the host.
    • A Linux or macOS host (or WSL2 on Windows).

    Quick start

    sh
    curl -fsSL https://intlayer.org/install.sh | sh

    What the installer does:

    1. Checks that docker and docker compose are present.
    2. Downloads docker-compose.yml and .env.example into ./intlayer/.
    3. If no .env exists, copies the example and generates random secrets for BETTER_AUTH_SECRET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY via openssl rand.
    4. Runs docker compose pull + docker compose up -d.
    5. Prints the URLs: dashboard :3000, API :3100, email UI :8025, MinIO console :9001.

    After the stack is up, open http://localhost:3000 and create your first account.


    Services

    Service Image Host port(s) Purpose
    app built from apps/app/Dockerfile 3000 TanStack Start dashboard (CMS UI)
    backend built from apps/backend/Dockerfile 3100 Fastify REST API (/health endpoint)
    mongo mongo:7 internal Single-node replica set (rs0)
    redis redis:7-alpine internal Job queues (BullMQ) and caching (ioredis)
    minio minio/minio 9000 (S3), 9001 (console) S3-compatible object storage for avatars and screenshots
    mailpit axllent/mailpit 1025 (SMTP), 8025 (web UI) Local transactional email sink

    Internal ports (mongo, redis) are not exposed to the host by default.

    MinIO port 9000 must be reachable by the browser because uploaded assets (avatars, screenshots) are loaded directly from S3_PUBLIC_URL=http://localhost:9000/intlayer.

    Environment variables

    The installer generates a ready-to-use .env. The table below describes every variable.

    Required (auto-generated or prompted)

    Variable Example Description
    NODE_ENV production Runtime environment
    PORT 3100 Backend listening port
    BACKEND_URL http://localhost:3100 Public URL of the backend API
    APP_URL http://localhost:3000 Public URL of the dashboard
    DOMAIN localhost Cookie domain
    MONGODB_URI mongodb://mongo:27017/intlayer?replicaSet=rs0 Full MongoDB connection URI
    REDIS_URL redis://redis:6379 Redis connection URL
    BETTER_AUTH_SECRET (generated) 32-byte secret for session signing
    MAIL_PROVIDER smtp Mail transport: smtp or resend
    MAIL_SMTP_HOST mailpit SMTP hostname (Mailpit container name)
    MAIL_SMTP_PORT 1025 SMTP port
    MAIL_FROM Intlayer <no-reply@localhost> Sender address
    S3_ENDPOINT http://minio:9000 S3-compatible endpoint
    S3_PUBLIC_URL http://localhost:9000/intlayer Public URL for browser asset loading
    S3_BUCKET_NAME intlayer Bucket name
    S3_ACCESS_KEY_ID (generated) MinIO access key
    S3_SECRET_ACCESS_KEY (generated) MinIO secret key
    VITE_BACKEND_URL http://localhost:3100 Backend URL baked into the dashboard at build time
    VITE_DOMAIN localhost Domain baked into the dashboard at build time

    Optional (features degrade gracefully when absent)

    Variable Feature
    OPENAI_API_KEY AI-assisted translation and content audit
    STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_* Billing and subscription management
    RESEND_API_KEY Transactional email via Resend (overrides Mailpit when set)
    GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET GitHub OAuth login
    GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET Google OAuth login
    GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET GitLab OAuth login
    MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET Microsoft OAuth login
    LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET LinkedIn OAuth login
    ATLASSIAN_CLIENT_ID, ATLASSIAN_CLIENT_SECRET Atlassian OAuth login

    Connecting your Intlayer project

    Once the stack is running, point your project at the self-hosted backend and dashboard instead of intlayer.org.

    Project configuration

    intlayer.config.ts
    import type { IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      editor: {
        clientId: process.env.INTLAYER_CLIENT_ID,
        clientSecret: process.env.INTLAYER_CLIENT_SECRET,
    
        /**
         * URL of the self-hosted CMS dashboard.
         * Default: https://app.intlayer.org
         */
        cmsURL: process.env.INTLAYER_CMS_URL, // e.g. http://localhost:3000
    
        /**
         * URL of the self-hosted backend API.
         * Default: https://back.intlayer.org
         */
        backendURL: process.env.INTLAYER_BACKEND_URL, // e.g. http://localhost:3100
      },
    };
    
    export default config;

    Set the environment variables in your project's .env:

    sh
    INTLAYER_CMS_URL=http://localhost:3000INTLAYER_BACKEND_URL=http://localhost:3100INTLAYER_CLIENT_ID=<your-client-id>INTLAYER_CLIENT_SECRET=<your-client-secret>

    Create access credentials in your self-hosted dashboard under Projects → Access keys at http://localhost:3000/projects.

    @intlayer/api SDK

    When using the @intlayer/api SDK programmatically, pass backendURL explicitly:

    cms.ts
    import { createIntlayerCMS } from "@intlayer/api";import { dictionaryEndpoint } from "@intlayer/api/dictionary";const cms = createIntlayerCMS({  editor: {    clientId: process.env.INTLAYER_CLIENT_ID,    clientSecret: process.env.INTLAYER_CLIENT_SECRET,    backendURL: process.env.INTLAYER_BACKEND_URL, // http://localhost:3100  },});const { data: dictionaries } = await dictionaryEndpoint(cms).getDictionaries();

    Upgrading

    Re-running the installer on an existing deployment performs a rolling upgrade:

    sh
    curl -fsSL https://intlayer.org/install.sh | sh

    This pulls the latest images and restarts containers with docker compose pull && docker compose up -d. Existing volumes (mongo-data, redis-data, minio-data) are preserved — no data loss.

    To upgrade manually from inside the ./intlayer/ directory:

    sh
    docker compose pulldocker compose up -d

    Backup and restore

    All persistent data lives in three named Docker volumes.

    Backup

    sh
    docker run --rm \  -v intlayer_mongo-data:/data \  -v "$(pwd)":/backup \  busybox tar czf /backup/mongo-data.tar.gz /datadocker run --rm \  -v intlayer_redis-data:/data \  -v "$(pwd)":/backup \  busybox tar czf /backup/redis-data.tar.gz /datadocker run --rm \  -v intlayer_minio-data:/data \  -v "$(pwd)":/backup \  busybox tar czf /backup/minio-data.tar.gz /data

    Restore

    sh
    docker run --rm \  -v intlayer_mongo-data:/data \  -v "$(pwd)":/backup \  busybox tar xzf /backup/mongo-data.tar.gz -C /# Repeat for redis-data and minio-data

    Using a reverse proxy (Nginx / Caddy)

    For production deployments, place a reverse proxy in front of the app and backend containers instead of exposing them directly.

    Nginx example

    nginx
    server {    listen 80;    server_name cms.example.com;    location / {        proxy_pass http://localhost:3000;        proxy_set_header Host $host;        proxy_set_header X-Real-IP $remote_addr;    }}server {    listen 80;    server_name api.example.com;    location / {        proxy_pass http://localhost:3100;        proxy_set_header Host $host;        proxy_set_header X-Real-IP $remote_addr;    }}

    Update the following .env variables to match your public domains:

    sh
    BACKEND_URL=https://api.example.comAPP_URL=https://cms.example.comDOMAIN=example.comVITE_BACKEND_URL=https://api.example.comVITE_DOMAIN=example.com
    VITE_* variables are baked into the dashboard image at build time. If you change them after the image is built, you need to rebuild the app image (docker compose build app) or use runtime config injection.

    Troubleshooting

    Backend crash-loops on first start

    MongoDB and Redis must be healthy before the backend starts. The compose file uses depends_on with condition: service_healthy. If you see repeated backend restarts, check that the mongo and redis healthchecks pass:

    sh
    docker compose psdocker compose logs mongodocker compose logs redis

    Dashboard cannot reach the API

    Verify that VITE_BACKEND_URL matches the URL where the backend is reachable from the browser (not the Docker network). If you changed the backend port or added a reverse proxy, rebuild the dashboard image:

    sh
    docker compose build appdocker compose up -d app

    Email not sending

    By default, all outbound email is captured by Mailpit. Open http://localhost:8025 to see sent messages. To send real email, set MAIL_PROVIDER=resend and RESEND_API_KEY=<your-key> in .env, then restart the backend:

    sh
    docker compose restart backend

    MinIO bucket missing

    If the minio-init one-shot service didn't run (or ran before MinIO was ready), create the bucket manually:

    sh
    docker compose run --rm minio-init