---
title: "Getting started with TinyMCE AI on-premises"
description: "Docker Compose quick start for TinyMCE AI on-premises service"
canonical_url: "https://www.tiny.cloud/docs/tinymce/latest/tinymceai-on-premises-getting-started/"
md_url: "https://www.tiny.cloud/docs/tinymce/latest/tinymceai-on-premises-getting-started/index.md"
version: "latest"
last_updated: "2026-09-11T04:25:32Z"
tokens: 4971
---
# Getting started with TinyMCE AI on-premises

This guide sets up a fully working local stack on any machine with Docker:

- **MySQL 8.0**: conversation history and metadata
- **Redis**: caching and session state
- **TinyMCE AI service**: the on-premises AI back end
- **A minimal token server** (Node.js): signs JWTs for the editor
- **A browser page with TinyMCE**: validates the end-to-end flow

The quick start is designed to validate the stack components before moving to a production deployment. Production engineers can review this section to understand the conceptual flow before continuing to [Production deployment](../tinymceai-on-premises-production/).

## Before you begin

Verify the following are installed and accessible:

```bash
docker --version   # 20.10+ required (or podman --version for Podman 4+)
node --version     # 18+ required
npm --version
```
Additionally, have the following credentials ready:

- A TinyMCE license key and container registry credentials (from the Tiny account representative)
- At least one LLM provider API key (OpenAI, Anthropic, or Google)

## Quick start with Docker Compose

### Create the project folder

```bash
mkdir tinymceai-onpremise && cd tinymceai-onpremise
```

### Authenticate with the container registry

The service image lives at `registry.containers.tiny.cloud/ai-service-tiny`.

For Docker:

```bash
docker login -u tiny -p [access-token] registry.containers.tiny.cloud
```
For Podman:

```bash
podman login -u tiny -p [access-token] registry.containers.tiny.cloud
```

> **Note:** All accounts authenticate with the username `tiny`. Replace `[access-token]` with the access token supplied by the Tiny account representative. If credentials have not been received, contact [Tiny Support](https://support.tiny.cloud/).

### Pull the AI service image

```bash
docker pull registry.containers.tiny.cloud/ai-service-tiny:latest
```
For Podman, substitute `podman pull`. For production, pin a specific version tag (for example `:5.1.0`) rather than `:latest` for repeatable deployments and to avoid unexpected breaking changes.

### Create `docker-compose.yml` (data layer)

This compose file starts the data layer services (MySQL and Redis) that the AI service depends on. The AI service itself is started separately in the next step, which allows upgrading or reconfiguring it independently.

Create the file with exactly the contents below. Indentation is two spaces, never tabs.

```yaml
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-changeme}
      MYSQL_DATABASE: ai_service
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  mysql_data:
```

> **Tip:** Pin `mysql:8.0`, not `mysql:8`. The `:8` tag resolves to the latest MySQL minor version, which may use authentication plugins or SQL modes incompatible with the AI service schema migrations. See [MySQL version pinning](../tinymceai-on-premises-database/#mysql-version-pinning) for details.
PostgreSQL is equally supported. See [Database, Redis, and storage](../tinymceai-on-premises-database/) for an equivalent compose file. Review the [PostgreSQL schema prerequisite](../tinymceai-on-premises-database/#postgresql-schema-prerequisite) before switching.

> **Note:** If the AI service needs to reach the host machine (for example a self-hosted Ollama running on the host), add `--add-host=host.docker.internal:host-gateway` to the `docker run` command in the Launch section below. Docker Desktop (macOS, Windows) and Podman 4+ auto-inject this alias; native Linux Docker does not.

### Create the `.env` file

```bash
# --- Required: AI service license key provided by Tiny ---
AI_LICENSE_KEY='paste-ai-license-key-here'

# --- Required: strong secret used to log into the Management Panel ---
MANAGEMENT_SECRET='replace-with-strong-secret'

# --- Required: database password (must match docker-compose.yml) ---
DB_PASSWORD='replace-with-db-password'

# --- Required: at least one LLM provider key ---
OPENAI_API_KEY='paste-openai-key-here'
# ANTHROPIC_API_KEY='paste-anthropic-key-here'
# GOOGLE_API_KEY='paste-google-key-here'

# --- Filled in after creating an environment (see "Create an environment and access key" below). Used by the token server, not the AI service. ---
AI_ENV_ID='paste-environment-id-here'
AI_API_SECRET='paste-api-secret-here'
```

> **Important:**
> - `AI_LICENSE_KEY` activates the AI service container.
> - The `license_key` init option in `tinymce.init()` activates premium TinyMCE features in the self-hosted editor.
> - These are different credentials from different sources — they are not interchangeable. See the [Credentials](../tinymceai-on-premises/#credentials) section on the Overview page.

### Start MySQL and Redis

```bash
docker compose up -d
```
Wait ~15 seconds for MySQL to initialize, then verify:

```bash
docker compose ps
```
Both data layer containers (MySQL and Redis) should report `healthy` in the STATUS column. If MySQL still shows `starting`, wait another 10 seconds and re-run.

### Launch the AI service

The AI service runs as a standalone container outside of the Docker Compose stack. This separation allows upgrading or reconfiguring the AI service without restarting the database and Redis.

First, find the Docker Compose network name (Docker creates it from the folder name):

```bash
docker network ls --format '{{.Name}}' | grep default
```
Use the matching network name in `--network` below. First, load the `.env` file:

```bash
set -a && source .env && set +a
```
Then start the AI service container. The `docker run` command maps the friendly `.env` names onto the service’s own environment variables — for example, `AI_LICENSE_KEY` is passed to the container as `LICENSE_KEY`, and `MANAGEMENT_SECRET` as `ENVIRONMENTS_MANAGEMENT_SECRET_KEY`:

```bash
docker run --init -d -p 8000:8000 \
  --network <compose-network-name> \
  --name ai-service \
  -e LICENSE_KEY="$AI_LICENSE_KEY" \
  -e ENVIRONMENTS_MANAGEMENT_SECRET_KEY="$MANAGEMENT_SECRET" \
  -e DATABASE_DRIVER='mysql' \
  -e DATABASE_HOST='mysql' \
  -e DATABASE_USER='root' \
  -e DATABASE_PASSWORD="$DB_PASSWORD" \
  -e DATABASE_DATABASE='ai_service' \
  -e REDIS_HOST='redis' \
  -e PROVIDERS='{"openai":{"type":"openai","apiKeys":["'"$OPENAI_API_KEY"'"]}}' \
  -e STORAGE_DRIVER='database' \
  -e ALLOWED_ORIGINS='http://localhost:3000' \
  -e ENABLE_METRIC_LOGS='true' \
  registry.containers.tiny.cloud/ai-service-tiny:latest
```

> **Important:** This launch command configures a single OpenAI provider, through the `PROVIDERS` value and `$OPENAI_API_KEY`. The AI service only receives the environment variables explicitly passed in the `docker run` command, so adding `PROVIDERS`, `MODELS`, or other provider settings to the `.env` file alone has no effect.
> 
> To use any other provider — `openai-compatible` (for example Databricks, Ollama, or vLLM), Azure, Amazon Bedrock, or Google Vertex — edit the `docker run` command directly: replace the `PROVIDERS` value and add a `MODELS` array. The `MODELS` variable is required for every provider except direct OpenAI, Anthropic, and Google, which have built-in model routing. See [LLM providers](../tinymceai-on-premises-providers/) for the full `PROVIDERS` and `MODELS` configuration.

> **Note:** The AI service contacts `license.containers.tiny.cloud` on startup to validate the license key. Ensure this endpoint is reachable from the container. No customer data is sent during this check.

> **Tip:** If the container already exists from a previous attempt, remove it first with `docker rm -f ai-service`.

> **Tip:** The network name returned by `docker network ls` already includes the `_default` suffix (e.g., `tinymceai-onpremise_default`). Use the full name as-is in `--network`. For multiple LLM providers, extend the `PROVIDERS` JSON: `{"openai":{…​},"anthropic":{…​}}`.

> **Note:** The launch command above starts the AI service with basic conversation support. To enable **web search** in conversations, add `WEBSEARCH_ENABLED='true'` and `WEBSEARCH_ENDPOINT` (pointing to a search backend) to the `docker run` command. See [Web scraping and web search](../tinymceai-on-premises-mcp/#web-scraping-and-search) for the full configuration, endpoint contracts, and a SerpAPI example.
For Podman, replace `docker run` with `podman run` and use a Podman pod instead of a compose network. See [Production deployment](../tinymceai-on-premises-production/) for Podman-specific guidance. See [Podman deployment](../tinymceai-on-premises-production/#_podman_deployment) for a full example.

For native databases (the database runs on the host or in a managed service rather than in Docker), drop the `--network` flag and set `DATABASE_HOST=host.docker.internal` (Docker Desktop and Podman 4+). On native Linux Docker, additionally pass `--add-host=host.docker.internal:host-gateway`.

Wait five seconds, then verify:

```bash
curl http://localhost:8000/health
```
Expected response:

```json
{"serviceName":"on-premises-http","uptime":5.123}
```
Successful boot log (`docker logs ai-service`)
```text
Connecting to database (driver=mysql host=mysql)
Running migrations...
Migrations complete: 32 tables ready
Connecting to Redis (host=redis:6379)
Redis connected
Server is listening on port 8000.
```

> **Warning:** If the container exits immediately, run `docker logs ai-service`. The most common causes are documented in the [Troubleshooting](../tinymceai-on-premises-troubleshooting/) guide. The top three are: malformed `AI_LICENSE_KEY` (line breaks from word wrap), missing PostgreSQL schema, and JSON syntax error in `PROVIDERS`.

### Create an environment and access key

The AI service isolates users into Environments. Each environment has its own access keys.

1. Open the Management Panel: **[http://localhost:8000/panel/](http://localhost:8000/panel/)**
2. Sign in using the `MANAGEMENT_SECRET` from `.env`.
3. Click **Create Environment** and give it a name (for example "Development").
4. Note the **Environment ID** displayed (a short identifier like `viOu8BnjJHb0HGK091p`).
5. Inside the environment, click **Create a new access key**.
6. **Copy the Environment ID and Access Key**.

Update `.env` with the new values:

```bash
AI_ENV_ID='paste-environment-id-here'
AI_API_SECRET='paste-api-secret-here'
```

> **Important:** Always create environments through the Management Panel UI. See the [JWT authentication](../tinymceai-on-premises-jwt/) guide for details on environment and access key management.

### Create the token server

The token server signs JSON Web Tokens (JWTs) for the editor. The Node.js example below is for the demo only; the [JWT authentication](../tinymceai-on-premises-jwt/) guide contains production-ready endpoints in 8 languages (Node, Django, Flask, Laravel, Rails, .NET, Go, Spring Boot).

Create `package.json`:

```json
{
  "name": "tinymceai-onpremise-demo",
  "private": true,
  "scripts": {
    "start": "node token-server.js"
  },
  "dependencies": {
    "dotenv": "^16.0.0",
    "express": "^4.18.0",
    "jsonwebtoken": "^9.0.0",
    "tinymce": "^8.5.0"
  }
}
```
Create `token-server.js`:

token-server.js
```javascript
require('dotenv').config({ override: true });
const express = require('express');
const jwt = require('jsonwebtoken');
const path = require('path');

const PORT = process.env.PORT || 3000;
const AI_ENV_ID = process.env.AI_ENV_ID;
const AI_API_SECRET = process.env.AI_API_SECRET;
const AI_SERVICE_URL = process.env.AI_SERVICE_URL || 'http://localhost:8000';

if (!AI_ENV_ID || !AI_API_SECRET) {
  console.error('ERROR: AI_ENV_ID and AI_API_SECRET must be set in .env');
  console.error('Create an environment first: visit http://localhost:8000/panel/');
  process.exit(1);
}

const app = express();
app.use(express.json());

app.use('/tinymce', express.static(path.join(__dirname, 'node_modules', 'tinymce')));

app.post('/api/ai-token', (req, res) => {
  const token = jwt.sign({
    aud: AI_ENV_ID,
    sub: 'demo-user-001',
    user: { name: 'Demo User', email: 'demo@example.com' },
    auth: {
      ai: {
        permissions: [
          'ai:conversations:*',
          'ai:models:agent',
          'ai:actions:system:*',
          'ai:reviews:system:*'
        ]
      }
    }
  }, AI_API_SECRET, { algorithm: 'HS256', expiresIn: '1h' });

  res.json({ token });
});

app.get('/', (req, res) => {
  res.send(`<!DOCTYPE html>
<html>
<head>
  <title>TinyMCE AI on-premises Demo</title>
  <script src="/tinymce/tinymce.min.js" referrerpolicy="origin"></script>
</head>
<body style="max-width: 900px; margin: 40px auto; font-family: system-ui;">
  <h1>TinyMCE AI on-premises Demo</h1>
  <p>Select text and use the AI toolbar, or open the AI chat sidebar.</p>
  <textarea id="editor"><p>Select this text and try the AI features above. Ask the AI to rewrite it, summarize it, or change the tone.</p></textarea>
  <script>
    tinymce.init({
      selector: '#editor',
      license_key: 'your-license-key',
      plugins: 'tinymceai',
      toolbar: 'undo redo | blocks | bold italic | tinymceai-chat tinymceai-review tinymceai-quickactions',
      height: 500,
      tinymceai_service_url: '${AI_SERVICE_URL}',
      tinymceai_token_provider: () =>
        fetch('/api/ai-token', { method: 'POST' })
          .then(r => r.json())
          .then(data => ({ token: data.token }))
    });
  </script>
</body>
</html>`);
});

app.listen(PORT, () => {
  console.log('Editor:     http://localhost:' + PORT);
  console.log('Token API:  http://localhost:' + PORT + '/api/ai-token');
  console.log('AI Service: ' + AI_SERVICE_URL);
});
```

> **Note:** Replace `'your-license-key'` in the `token-server.js` listing with the TinyMCE editor license key. This is the editor license key described in the [Credentials](../tinymceai-on-premises/#credentials) section, not the AI service `AI_LICENSE_KEY`. For details on obtaining and applying it, see [License key](../license-key/).

### Install and run

```bash
npm install
```
The TinyMCE AI plugin must be present in the TinyMCE plugins directory before starting the demo. Copy the `tinymceai` folder into `node_modules/tinymce/plugins/`, or use the [`external_plugins`](../editor-important-options/#external_plugins) option to load it from a separate path.

```bash
npm start
```

> **Tip:** If port 3000 is already in use from a previous run, stop the existing process first:
> 
> ```bash
> lsof -ti :3000 | xargs kill
> ```

### Open the demo

Open **[http://localhost:3000](http://localhost:3000/)** in a browser. The editor loads with the AI toolbar. Select text and try the AI features. Responses stream in real time from the chosen large language model (LLM) provider, processed entirely within the local infrastructure.

The TinyMCE AI on-premises service is now running.

## Verifying the installation

After completing the quick start, run each check below from the command line to exercise the pipeline end-to-end.

### Step 1: Health check

**What this checks:** the AI service container is running and connected to the database and Redis.

```bash
curl http://localhost:8000/health
```
**Expected response:**

```json
{"serviceName":"on-premises-http","uptime":12.345}
```
A JSON response with `serviceName` and `uptime` confirms the container is healthy. If the request fails or times out, check `docker logs ai-service` for startup errors.

### Step 2: Generate a token

**What this checks:** the token server can sign a valid JWT using the API Secret and Environment ID.

```bash
curl -s -X POST http://localhost:3000/api/ai-token | python3 -m json.tool
```
**Expected response:**

```json
{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}
```
A JSON response containing a `token` field confirms the token server is running and can sign JWTs. If the server returns an error, verify that `AI_ENV_ID` and `AI_API_SECRET` are set in `.env`.

### Step 3: Create a conversation and send a message

**What this checks:** the full chain — JWT verification, permissions, environment registration, LLM provider authentication, and SSE streaming.

```bash
TOKEN=$(curl -s -X POST http://localhost:3000/api/ai-token | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")

curl -s -X POST http://localhost:8000/v1/conversations \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":"verify-1","title":"Verification"}'
```

> **Note:** The command below uses the built-in `agent-1` model. If `MODELS` has been explicitly configured, replace `agent-1` with the `id` of one of the configured models. See [Defining the model list](../tinymceai-on-premises-providers/#models-required).

```bash
curl -s -N -X POST http://localhost:8000/v1/conversations/verify-1/messages \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Say hello in five words.","model":"agent-1"}'
```
**Expected response:** a Server-Sent Events stream:

```text
event: message-metadata
data: {"messageId":"abc123"}

event: text-delta
data: {"textDelta":"Hello "}

event: text-delta
data: {"textDelta":"there, "}

event: text-delta
data: {"textDelta":"friend!"}

event: done
data: {}
```
A stream of `text-delta` events followed by `done` confirms the entire pipeline is working: container health, database connectivity, Redis connectivity, JWT signing and verification, permissions, environment registration, LLM provider authentication, and SSE streaming.

If the stream emits `event: error`, inspect the `data` payload. Provider errors (invalid API key, IAM denial, model unavailable) ride inside the Server-Sent Events (SSE) response. The HTTP status stays 200. See the [LLM provider errors](../tinymceai-on-premises-troubleshooting/) section in the Troubleshooting guide for details.

## Updating configuration

> **Important:** After changing `.env` values, containers must be recreated to pick up new environment variables. A simple restart (`docker restart` or `docker compose restart`) preserves the old values.

```bash
# Recreate the data layer (MySQL, Redis):
docker compose up -d --force-recreate

# Recreate the standalone AI service:
docker stop ai-service && docker rm ai-service
# Then re-run the launch script from "Launch the AI service" above.
```
For Kubernetes, update the Secret and trigger a rollout restart:

```bash
kubectl rollout restart deployment/ai-service -n tinymce-ai
```

## Stopping and cleaning up

```bash
# Stop the AI service (standalone Docker)
docker stop ai-service && docker rm ai-service

# Stop the Docker Compose stack
docker compose down

# Remove all data including volumes (destructive)
docker compose down -v
```
For Kubernetes, scale the deployment to zero or delete it. Persistent volumes for the database are retained unless explicitly deleted.

```bash
kubectl delete deployment ai-service -n tinymce-ai
```

## Next steps

The quick start validates the stack end-to-end on a single machine. To deploy for production, work through each guide in order:

1. [Database, Redis, and storage](../tinymceai-on-premises-database/): provision managed databases, configure TLS, and set up production-grade file storage.
2. [LLM providers](../tinymceai-on-premises-providers/): configure explicit model catalogs and multi-provider routing.
3. [JWT authentication](../tinymceai-on-premises-jwt/): build the production token endpoint with proper permissions and multi-tenant isolation.
4. [TinyMCE integration](../tinymceai-on-premises-frameworks/): wire the editor to the production AI service with CORS and CSP.
5. [Production deployment](../tinymceai-on-premises-production/): deploy to Kubernetes or ECS with TLS, scaling, and observability.
