--- createdAt: 2026-06-30 updatedAt: 2026-06-30 title: Self-Hosting Intlayer description: Run a complete Intlayer instance on your own infrastructure with a single command. No Intlayer Cloud account required. keywords: - Self-Hosting - Docker - Docker Compose - Intlayer - CMS - Installation - Infrastructure slugs: - doc - self-hosting author: aymericzip --- # 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 ``` ┌─────────────────────────────┐ 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 ` | 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 ```typescript fileName="intlayer.config.ts" codeFormat={["typescript", "esm", "commonjs"]} 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:3000 INTLAYER_BACKEND_URL=http://localhost:3100 INTLAYER_CLIENT_ID= INTLAYER_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: ```typescript fileName="cms.ts" codeFormat="typescript" 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 pull docker 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 /data docker run --rm \ -v intlayer_redis-data:/data \ -v "$(pwd)":/backup \ busybox tar czf /backup/redis-data.tar.gz /data docker 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.com APP_URL=https://cms.example.com DOMAIN=example.com VITE_BACKEND_URL=https://api.example.com VITE_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 ps docker compose logs mongo docker 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 app docker 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=` 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 ``` --- ## Useful links - [Intlayer CMS documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md) - [Configuration reference](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/configuration.md) - [CMS SDK — `@intlayer/api`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md#programmatic-access-with-the-intlayerapi-sdk)