The self-hosted AI project control plane for real workspaces, scheduled runs, and safe deploys.
Minnas is a self-hosted AI project control plane. It gives you a web UI to manage real git workspaces, run Codex-driven assistant/build jobs, automate recurring runs, and deploy with Kamal—all on your own infrastructure.
Go further by chatting with AI with tools provided by the projects you deploy. Schedule runs in one place, to hit all the projects you've deployed. Set up push notifications to be triggered when scheduled runs finish.
AI is great at a lot of things, and we've seen it get better and better at running skills, commands, and managing a computer. But these things are slow to interact with if I want to make a quick change to something (say toggle a todo), I shouldn't need to go through a chat interface to do that. Coding Agents are pretty good at one-shotting mini websites to do a small task, that's where Minnas comes in.
Minnas will help you deploy projects to your own self-hosted network, building out actions for you to interact with them via chat agent, but you can also go to them as a hosted service to do what you want as well.
Minnas also deploys as a web-service, think of it as your own personal SaaS assistant. It runs on coding agents like Codex (soon to come are Cursor, and Github Copilot), which can run CLI tools if you ask it too, and Minnas allows it out of the box (runs with --yolo). It can do some dangerous stuff. Though, I trust that it has been trained to avoid doing those things without some major prompting to do it.
- Project workspaces with persistent logs, artifacts, uploads, and run history.
- Per-project chats plus a global assistant inbox (threaded conversations, image attachments).
- On-demand runs (assistant + build) with live log streaming.
- Scheduled runs (interval, daily, weekly) scoped to a project or global.
- Action registry from
minnas.actions.jsonso Codex can call your HTTP endpoints; review/edit in the Actions screen. - Kamal deploys with saved defaults, templated deploy.yml, and optional simulated deploys.
- Web app: Next.js UI + API routes.
- Worker: Node process that runs Codex and Kamal.
- Storage: SQLite + filesystem volumes (
/data,/workspaces).
cp .env.example .env
# set SESSION_SECRET and (optionally) ADMIN_EMAIL
docker compose up --build- Visit
/setupto create the initial admin account. - If
ADMIN_EMAILis set, the account email must match it.
- DB:
/data/app.db - Logs:
/data/logs/<runId>.log - Artifacts:
/data/artifacts/<runId>/ - Uploads:
/data/uploads/<messageId>/... - Workspaces:
/workspaces/<projectId>/repo
- Go to Admin → Codex Auth and run device auth.
- Codex auth is stored in
CODEX_HOME(default/data/.codex).
- Go to Admin → SSH keys to generate a deploy keypair.
- Add the public key to your target host’s
~/.ssh/authorized_keys. - The private key is stored at
SSH_PRIVATE_KEY_PATH(default/data/.ssh/id_ed25519).
- You can attach an image in chat.
- The image is stored locally (in
/data/uploads/...). - The Codex prompt is augmented with the local image path.
Minnas can discover actions defined in a project workspace minnas.actions.json file and publish them as tools for Codex runs.
- Actions are validated and surfaced in Actions for review/editing.
- During a run, Minnas can call the action HTTP endpoint against the project's configured deploy target.
- Create interval, daily, or weekly schedules for assistant or build runs.
- Schedules can target a specific project or the global assistant.
Fool‑proof flow:
- Create a deploy target (host + SSH user/port).
- Save the project deploy config. Saving writes
config/deploy.ymlinto the project workspace. - Click Deploy.
The worker always regenerates config/deploy.yml before running Kamal, so the latest UI config is what gets deployed (and it will commit changes into the project workspace before deploying).
Set defaults in Admin → Runtime settings so new projects require minimal setup:
- Default image prefix (e.g.
ghcr.io/your-org) - Default registry server/username
- Registry password secret name (value stored in
.kamal/secrets)
If you paste a custom deploy.yml, you can use {{token}} placeholders. Missing values cause a clear error.
Available tokens:
{{app.name}}{{app.image}}(includes default image prefix){{app.image_raw}}{{service.port}}{{registry.server}}{{registry.username}}{{registry.password_secret}}{{ssh.user}}{{ssh.port}}{{ssh.key_path}}{{proxy.hosts}}(comma‑separated)
Example:
image: {{app.image}}
registry:
server: {{registry.server}}
username: {{registry.username}}
password:
- {{registry.password_secret}}
ssh:
user: {{ssh.user}}
port: {{ssh.port}}
keys:
- {{ssh.key_path}}- Enable SSH on your machine.
- Ensure Docker is running.
- Add the host key so Kamal doesn’t prompt during deploy:
ssh-keyscan host.docker.internal >> ~/.ssh/known_hosts- In the Deploy tab, use:
- Host:
host.docker.internal - SSH user: your local username
- Port: 22
Notes:
- On Linux,
host.docker.internalmight not resolve; use your machine’s LAN IP. - Ensure your SSH user can run Docker on the target host.
If your app is only reachable on a private network (e.g. minnas.sombra), Let’s Encrypt cannot issue a cert. Use a private CA with mkcert and load the PEMs via Kamal secrets.
- Create a private cert folder (keep it out of git):
mkdir -p private/certs
echo "private/certs/" >> .gitignore- Generate a private CA and cert:
brew install mkcert
mkcert -install
cd private/certs
mkcert minnas.sombra- Save the root CA so you can install it on devices:
cp "$(mkcert -CAROOT)/rootCA.pem" private/certs/- Store the certs in Kamal secrets:
kamal secrets set MINNAS_CERT_PEM="$(cat private/certs/minnas.sombra.pem)"
kamal secrets set MINNAS_CERT_KEY_PEM="$(cat private/certs/minnas.sombra-key.pem)"- In the Deploy UI, set a Custom deploy.yml (so it is not overwritten):
proxy:
app_port: 3000
hosts:
- minnas.sombra
ssl:
certificate_pem: MINNAS_CERT_PEM
private_key_pem: MINNAS_CERT_KEY_PEM
forward_headers: true- Install
rootCA.pemon each device that needs to trust the cert (macOS Keychain or iOS profile), then redeploy.
Core:
ADMIN_EMAIL: email allowed to bootstrap the first user.SESSION_SECRET: HMAC secret for session cookies.DATABASE_URL: defaultfile:/data/app.db.DATA_DIR: override data path (default/dataif present).WORKSPACES_DIR: override workspaces path (default/workspacesif present).
Codex:
CODEX_API_KEY: API key for Codex CLI.CODEX_HOME: default/data/.codex.CODEX_SANDBOX: override sandbox (e.g.workspace-write).CODEX_STATUS_TTL_SECONDS: cache Codex availability checks (default60).
Deploy:
SSH_PRIVATE_KEY_PATH: default/data/.ssh/id_ed25519.SIMULATE_DEPLOY: set1to force deploy simulation.KAMAL_SETUP: set1to runkamal setupbefore deploy.KAMAL_QUIET: set1for quieter output.KAMAL_LOG_MODE:defaultorerrors.KAMAL_BUILD_CLEANUP: set0to disable build cache cleanup.PROXY_HOSTS: comma-separated default hosts.WORKER_CONCURRENCY: number of jobs the worker can run in parallel (default1).WORKER_PROJECT_LOCK: set0to allow concurrent runs on the same project (default1).WORKER_PROJECT_LOCK_SCOPE:kind(default) allows different run types in parallel;projectblocks all runs per project.
Deploy defaults (admin override env):
DEFAULT_IMAGE_PREFIXDEFAULT_REGISTRY_SERVERDEFAULT_REGISTRY_USERNAMEDEFAULT_REGISTRY_PASSWORD_SECRET
npm run dev # Next.js dev server
npm run worker:dev # Worker loop (ts)
npm run db:generate # Generate drizzle migrationsInstall the Codex CLI (needed for the worker):
npm i -g @openai/codexAuthenticate (local dev):
codex login
