Ask your question and get a summary of the document by referencing this page and the AI provider of your choice
The content of this page was translated using an AI.
See the last version of the original content in EnglishIf you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
Self-Hosting Intlayer
Intlayer can run entirely on your own infrastructure — no Intlayer Cloud account required. A single command boots a production-ready stack:
Copy the code to the clipboard
curl -fsSL https://intlayer.org/install.sh | shThe 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
Copy the code to the clipboard
┌─────────────────────────────┐ 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, and9001available on the host. - A Linux or macOS host (or WSL2 on Windows).
Quick start
Copy the code to the clipboard
curl -fsSL https://intlayer.org/install.sh | shWhat the installer does:
- Checks that
dockeranddocker composeare present. - Downloads
docker-compose.ymland.env.exampleinto./intlayer/. - If no
.envexists, copies the example and generates random secrets forBETTER_AUTH_SECRET,S3_ACCESS_KEY_ID, andS3_SECRET_ACCESS_KEYviaopenssl rand. - Runs
docker compose pull+docker compose up -d. - 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
Open the table in a modal to view all data content clearly
| 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 port9000must be reachable by the browser because uploaded assets (avatars, screenshots) are loaded directly fromS3_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)
Open the table in a modal to view all data content clearly
| 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)
Open the table in a modal to view all data content clearly
| 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
Copy the code to the clipboard
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:
Copy the code to the clipboard
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:
Copy the code to the clipboard
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:
Copy the code to the clipboard
curl -fsSL https://intlayer.org/install.sh | shThis 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:
Copy the code to the clipboard
docker compose pulldocker compose up -dBackup and restore
All persistent data lives in three named Docker volumes.
Backup
Copy the code to the clipboard
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 /dataRestore
Copy the code to the clipboard
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-dataUsing 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
Copy the code to the clipboard
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:
Copy the code to the clipboard
BACKEND_URL=https://api.example.comAPP_URL=https://cms.example.comDOMAIN=example.comVITE_BACKEND_URL=https://api.example.comVITE_DOMAIN=example.comVITE_*variables are baked into the dashboard image at build time. If you change them after the image is built, you need to rebuild theappimage (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:
Copy the code to the clipboard
docker compose psdocker compose logs mongodocker compose logs redisDashboard 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:
Copy the code to the clipboard
docker compose build appdocker compose up -d appEmail 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:
Copy the code to the clipboard
docker compose restart backendMinIO bucket missing
If the minio-init one-shot service didn't run (or ran before MinIO was ready), create the bucket manually:
Copy the code to the clipboard
docker compose run --rm minio-init