Skip to content

Deployment

Run mikan under PM2 with persistent state, managed sandbox images, graceful shutdown, and an optional health endpoint.

PM2 supervisor

PM2 daemonizes mikan, restarts it after failures, retains stdout/stderr logs, and can start it on boot.

Managed image sandbox

The mikan host process uses Docker to create per-conversation tool containers from the selected sandbox image.

Persistent state

Keep one host-only state directory for settings, the office registry, vaults, per-office state, and GitHub polling watermarks.

  • Node.js >=24.15.0
  • at least one complete Slack, Telegram, Discord, or GitHub credential set
  • a persistent ~/.mikan for the PM2 user, where mikan keeps its state; the working directory defaults to ~/.mikan/workspace
  • A container engine available to the PM2 user when using oci:* or container:*: nerdctl, Podman, or Docker; mikan uses the first one running (see OCI sandbox)

The published mikan-sandbox image is a tool runtime, not a standalone mikan server image. PM2 runs mikan on the host; oci mode creates and manages containers through the host Docker daemon.

  1. Install mikan and PM2:

    Terminal window
    npm i -g @geminixiang/mikan pm2
  2. Run the setup as the user PM2 runs under. It asks for the chat platform’s tokens, the LLM provider, and the sandbox image, then writes ~/.mikan/settings.json, the secrets file ~/.mikan/mikan.env (mode 0600), and the PM2 file ~/.mikan/ecosystem.config.cjs:

    Terminal window
    mikan onboard

    Every secret lives in ~/.mikan/mikan.env, which the ecosystem file loads at start; never put secrets in the ecosystem file or PM2’s own environment. To add a platform or variable later, edit that file; mikan env lists every variable and what is currently set. An existing ~/.mikan/ecosystem.config.cjs is kept as it is.

  3. Start and persist the process:

    Terminal window
    pm2 start ~/.mikan/ecosystem.config.cjs
    pm2 save
    pm2 startup
  4. Run the command printed by pm2 startup to enable boot startup.

The ecosystem file is supervision-only: process name, restart policy, graceful-shutdown timeout, the mikan.env loader, and the sandbox mode in args (run mikan --help for the flag reference). Behavior (model, sandbox limits, reply modes) stays in ~/.mikan/settings.json. The working-directory argument is optional; without it mikan uses ~/.mikan/workspace and creates it on first start.

Relative workspace paths are resolved from PM2’s current working directory. Absolute paths avoid silently opening a different workspace after service migration or reboot.

Terminal window
npm i -g @geminixiang/mikan
pm2 reload mikan

If the new version has pending State migrations, run them before reloading.

After editing ~/.mikan/mikan.env, reload through the ecosystem file so the environment is re-read:

Terminal window
pm2 reload ~/.mikan/ecosystem.config.cjs

A release that changes the format of persisted files ships a numbered migration. The daemon refuses to start while one is pending and prints the exact mikan migrate command to run; it never converts files on its own. ~/.mikan/migrations.json records which migrations have been applied.

Check for pending migrations after installing a new version. --dry-run only reads, so it is safe while the daemon runs. Pass the same --sandbox and workspace the daemon uses:

Terminal window
mikan migrate --sandbox=oci:mikan-sandbox:latest --dry-run

If it prints No pending migrations., reload as above. Otherwise stop the daemon, apply them, and start it again:

Terminal window
pm2 stop mikan
mikan migrate --sandbox=oci:mikan-sandbox:latest
pm2 start mikan

A migration stops, without being recorded, when it cannot decide safely: for example, a directory whose name fits several enabled platforms (bare digits with both Telegram and Discord running), or a conversation present under both an old and a new name. The error names each path and the fix. Name an ambiguous owner with --owner <conversationId>=<platform> (repeatable) and run again; the migrations that already succeeded stay applied.

1.0.0 migrates a 0.5.3 install in place. It is the only supported upgrade: formats that existed only in 1.0.0 prereleases are not converted. Until the migrations run, the 1.0 daemon refuses to start and prints the mikan migrate command for your install.

  1. Back up. Stop the 0.5.3 daemon and copy both its state directory and its working directory. Migrations rename directories in both.

  2. Install 1.0. It needs Node.js 24.15.0 or newer: npm i -g @geminixiang/mikan@latest.

  3. Move state to ~/.mikan. 1.0 has no --state-dir; if 0.5.3 kept its state elsewhere, move that directory to ~/.mikan, and remove --state-dir from the command line your process manager runs.

  4. Choose a supported sandbox. 1.0 runs host, container:<name>, and oci:<image>. A 0.5.3 install on firecracker:* or cloudflare:* must pick one of these first.

  5. Move provider keys. Keys that 0.5.3 read from ~/.pi/mikan/auth.json are not migrated; set them in ~/.mikan/mikan.env, or keep them in models.json.

  6. Migrate. Pass the 0.5.3 working directory and the sandbox the daemon will run with, preview, then apply:

    Terminal window
    mikan migrate --workspace /srv/mikan --sandbox=oci:mikan-sandbox:latest --dry-run
    mikan migrate --workspace /srv/mikan --sandbox=oci:mikan-sandbox:latest
  7. Start and check the log. For each settings.json, the daemon logs Settings that mikan ignores; remove them from <path> with the 0.5.3 keys 1.0 no longer reads, such as llm.autoReply, and Environment variable that mikan ignores for each variable it no longer reads, with what replaces it. Remove them; move a sentry.dsn setting to SENTRY_DSN.

mikan migrate:

  • renames conversation directories and their vaults from raw conversation ids to office keys;
  • renames host per-user vaults and the container:* vault to hashed identity keys;
  • moves each conversation’s settings.json into ~/.mikan/conversations/<office-key>/;
  • imports each conversation’s session files into its sessions.db and keeps the original files under ~/.mikan/conversations/<office-key>/sessions-v3/;
  • moves <workspace>/events/*.json into each conversation’s host-only event store;
  • removes the 0.5.3 sandbox containers, which are recreated from the current image on the next message, with an empty /root;
  • copies ~/.pi/agent/models.json (or $PI_CODING_AGENT_DIR/models.json) to ~/.mikan/models.json when mikan does not have one yet;
  • removes last_prompt.jsonl and auto-reply.disabled from each conversation directory, since 1.0 writes neither.

What people notice after the upgrade:

  • Only session and stop work without a leading slash; new, model, and auto-reply need their slash forms, such as /pi-new.
  • Auto-reply is Slack-only and takes on, off, or jev. A channel that had auto-reply rules now replies to every top-level message; use /pi-auto-reply off or jev to narrow it.
  • Inside the sandbox, a conversation’s files are at /workspace/<office-key>. Paths the agent quoted in earlier sessions point at the old /workspace/<conversation-id> location.
  • Extensions are no longer loaded; skills in skills/ directories still are.

The changelog lists every change since 0.5.3, including the narrower package interface for embedders.

The portal server exists only when LINK_PORT is set, or when LINK_URL causes the default port 8181 to be used:

Terminal window
curl http://127.0.0.1:8181/health
# {"ok":true}

/health is a liveness check for the HTTP process only. It does not verify platform connections, Docker, sandbox provisioning, LLM providers, or event delivery, so do not use it as the only readiness signal.

See the maintained PM2 ecosystem file on GitHub and Sandbox modes for mode-specific requirements.