SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-13

How to Self-Host Octop with Docker Compose

Run Octop v0.9.19 for multiple users on one VPS with Docker Compose, private workspaces, OpenAI-compatible models and TLS. Skip the curl installer.

Wetin be Octop, and why you fit self-host am

Octop na self-hosted AI assistant for household or small team. The reason to self-host Octop instead of plain chat front end na say e keep users separate from each other. Open WebUI give you browser interface wey dey in front of model. Octop add accounts with admin role, private workspace and credential set for each user, plus library of specialist agents wey each user fit switch between based on task. Na this difference make one VPS fit serve five people instead of one.

The project dey for github.com/TencentCloud/Octop. Na one process wey serve web dashboard, command line interface, chat channels (Feishu, DingTalk, QQ, Discord, WeCom), and scheduled jobs. Everything dey backed by one SQLite database under ~/.octop/. Everything below dey written against tag v0.9.19, wey dem release on 5 August 2026. If you never decide which platform to use, the comparison of Open WebUI alternatives wey you fit run for VPS cover the wider field.

Make one thing clear before you spend evening on am. Octop na pre-1.0 software wey vendor publish from e GitHub organisation, with around 900 stars as of August 2026. E dey change fast, and the version numbers show am. Nothing for here promise stable upgrade path. Pin a tag, read the changelog, and keep backups.

Wetín you need before you start

  • One VPS wey dey run Ubuntu 24.04 with Docker Engine and the Compose plugin. If Compose new to you, start with the Docker Compose basics for VPS.
  • git, because you go check out one release tag instead of pulling an image.
  • One domain name wey dey point to the VPS, because you want TLS (transport layer security) for front of am.
  • One model backend wey dey speak OpenAI API: local Ollama, self-hosted gateway, or paid key.

Octop itself no heavy. Na Python process and SQLite file e be. The model backend carry most of the load, so if you plan run the model for the same box, choose box size wey fit handle the model.

Why we no recommend the curl installer

The README start with one-line install:

curl -fsSL https://finnie-1258344699.cos.ap-guangzhou.myqcloud.com/octop/install.sh | bash

We no recommend am for server wey matter to you, for one clear reason: that script no dey inside the repository. Tencent Cloud Object Storage bucket dey serve am. No git tag or commit dey cover am, so you no fit compare today's script with last week's own, and no history dey explain any change. The bucket fit serve different bytes tomorrow, and nothing for the project go record am. If you pipe the result straight into bash, the machine go run the script before you don read even one line.

The installer still dey write directly to the host instead of container. E use uv to fetch Python 3.12 and build environment wey your package manager no know anything about, so removing am later go be manual work.

Two better options dey. Fetch the script, read am, then run am. E go take thirty seconds: curl -fsSL <url> -o install.sh, then less install.sh, then bash install.sh. Or use Docker, wey na the rest of this guide dey explain. The PyPI package (pip install octop) at least na versioned artifact wey you fit pin to a release.

Deploy Octop with Docker Compose, pin am to v0.9.19

As of August 2026, no published image dey available to pull. The Compose file wey dem ship dey build the image from the repository, so to pin version mean say you go checkout git tag.

git clone https://github.com/TencentCloud/Octop.git
cd Octop
git checkout v0.9.19

Na this service the file define, with only the important parts:

services:
  octop:
    build:
      context: ..
      dockerfile: docker/Dockerfile
    image: octop:latest
    container_name: octop
    restart: unless-stopped
    ports:
      - "${OCTOP_PORT:-8088}:${OCTOP_PORT:-8088}"
    volumes:
      - ${OCTOP_DATA:-~/.octop}:/data/.octop
    environment:
      - HOME=/data
      - OCTOP_BIND_HOST=0.0.0.0
      - OCTOP_PORT=${OCTOP_PORT:-8088}
      - OCTOP_DEFAULT_PASSWORD=${OCTOP_DEFAULT_PASSWORD:-octop}
      - OCTOP_ADMIN_USERNAME=${OCTOP_ADMIN_USERNAME:-admin}
      - OPENAI_API_KEY=${OPENAI_API_KEY:-}

Notice the build: block. image: octop:latest na the name wey your own build get, e no be registry reference, so latest for here mean anything wey you compile most recently. Set the data path to a clear location instead of leaving am for default, and give the admin account real password before first boot. Put this for docker/.env:

OCTOP_PORT=8088
OCTOP_ADMIN_USERNAME=admin
OCTOP_DEFAULT_PASSWORD=<a long random password>
OCTOP_DATA=/srv/octop-data

One trap for here important pass the rest of the file. Compose dey read docker/.env only to interpolate ${...} placeholders for the YAML. Any key wey you add to that file no go reach the container unless you also list am under environment: for the Compose file. Add OCTOP_ACCESS_TOKEN_TTL to .env alone and e no do anything, silently. The other option na to write the same keys inside ~/.octop/env for the mounted data directory, wey Octop dey load when e start. The guide about env files and secrets for Docker Compose explain why these two mechanisms no be the same thing.

Build and start am:

docker compose -f docker/docker-compose.yml up -d --build
docker compose -f docker/docker-compose.yml ps
curl http://127.0.0.1:8088/api/health

Healthy instance go answer the health check with {"status":"ok","version":"..."}. If e return anything else, read docker compose -f docker/docker-compose.yml logs -f octop before you touch the browser.

Now give the image wey you just build a meaningful name, because the next --build go overwrite octop:latest and you no go fit tell the two apart:

docker image tag octop:latest octop:0.9.19

For first boot, octop init go run and write the starting credentials inside the data volume:

docker exec -it octop cat /data/.octop/credential.txt

The defaults na admin / octop, and dem dey apply only on first init. Na this explain why people dey ask this question plenty: if you change OCTOP_DEFAULT_PASSWORD after the container don start once, e no go change anything, because the account don already exist. Change the password from the dashboard instead.

No publish port 8088

The ports: line above bind every interface for the VPS. As container start, dashboard don enter public internet for cleartext, with default password. Octop own OCTOP_BIND_HOST default na 127.0.0.1; the Compose file override am to 0.0.0.0 because the process must accept traffic from outside its own network namespace. That override correct. Na the published port dey expose you.

Edit the ports: line for docker/docker-compose.yml so the mapping go listen only on loopback:

    ports:
      - "127.0.0.1:${OCTOP_PORT:-8088}:${OCTOP_PORT:-8088}"

No try use plain override file fix this one. Compose dey join the ports lists from multiple files instead of replacing dem, so you go end up publishing both mappings and the second one go fail to bind. If you want leave the upstream file untouched, use the !override tag for the sequence. Na the documented way to replace am instead of append. The explanation of how Compose dey merge multiple files cover the remaining merge rules.

Binding to loopback also solve problem wey you for face with firewall. Docker dey write its published-port rules inside the nat table before the chains wey ufw dey manage, so ufw deny 8088 no dey stop published container port. Port wey bind to 127.0.0.1 no fit reachable from outside, regardless of wetin ufw think, and na why this one be the correct fix instead of second-best option.

Put TLS for front with reverse proxy

Caddy na the shortest way, because e dey request the certificate through ACME (automatic certificate management environment) by itself, and e dey proxy WebSockets without extra configuration:

octop.example.com {
    reverse_proxy 127.0.0.1:8088
}

nginx need more care, because Octop dey stream chat through WebSocket:

server {
    listen 443 ssl;
    server_name octop.example.com;

    ssl_certificate     /etc/letsencrypt/live/octop.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/octop.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8088;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}

Every line for there get work wey e dey do. Chat dey run through WS /agents/{id}/chat/ws, so if proxy_http_version 1.1 and the two upgrade headers no dey, nginx go answer the upgrade attempt with 400 Bad Request: dashboard go load normally, but every message wey you send go hang forever without error for the page. proxy_buffering off matter because the human-in-the-loop resume endpoint dey return text/event-stream, and SSE (server-sent events) wey proxy buffer hold go reach as one lump for the end instead of streaming. proxy_read_timeout cover long tool runs, because the 60 second default fit cut agent off for middle of task and logs go show upstream timed out (110: Connection timed out).

JWT auth proxy behind how e dey behave

Octop dey authenticate with bearer token, no be cookie. POST /api/auth/login dey return {access_token, role, user, ...} and later calls carry Authorization: Bearer <access_token>. For reverse proxy, na good news: cookie domain no dey, Secure flag no dey, and SameSite rule no dey wey you fit configure wrong. So session wey work for http://127.0.0.1:8088 go behave the same way for https://octop.example.com.

Two things dey important to know before you put real users on am.

WebSocket dey carry the token for URL. The endpoint na WS /agents/{id}/chat/ws?token=<jwt>, because browser JavaScript no fit set Authorization header for WebSocket handshake. TLS dey protect the token while e dey travel. But e no protect am from your own logs: nginx dey write the complete request line, including query string, to access_log by default. This mean working token for real user fit end up inside plaintext file for server. Log the path without the arguments. $uri na the normalised path wey don already remove query string, so put this inside http block and reference am from the server:

log_format octop_noargs '$remote_addr [$time_local] '
                        '"$request_method $uri $server_protocol" '
                        '$status $body_bytes_sent';
access_log /var/log/nginx/octop.log octop_noargs;

Per-session logout no dey. OCTOP_ACCESS_TOKEN_TTL default na 86400, so token dey remain valid for 24 hours after login. The only documented way to invalidate one na octop admin rotate-jwt-secret. This one dey rotate the signing key wey dey stored for ~/.octop/secrets/jwt_secret and immediately invalidate every token wey still dey valid, for everybody. So when person leave the team, follow this order: delete the user, rotate the secret, then tell the remaining users make dem log in again. If this one too heavy, reduce the lifetime, but remember to add the variable to environment: list and .env too:

OCTOP_ACCESS_TOKEN_TTL=28800

Brute force don dey handled: OCTOP_LOGIN_MAX_ATTEMPTS default na 5 failures and OCTOP_LOGIN_LOCKOUT_SECONDS na 900. So user wey lock out just dey wait fifteen minutes, instead make e think say the installation spoil. Octop get im own user store and no documented OIDC support for v0.9.19. If you need real single sign-on, put authenticating proxy in front of am. Na this work self-hosted Authentik server dey do.

Point Octop go model backend

Providers dey configured per agent for dashboard, and octop provider list dey show you wetin dem set. Octop ships presets for OpenAI-compatible APIs, DashScope (Qwen) and Ollama, and dem dey store the credentials for providers table inside your own SQLite database. This choice dey change wetin you go pay and wetin go comot from the server.

Local model with Ollama. Nothing go comot from the server, and you go pay with RAM instead of tokens. The wiring detail wey dey catch people be say: container no fit reach host's Ollama for 127.0.0.1:11434, because that address na the container's own loopback. Add host gateway entry to the service:

    extra_hosts:
      - "host.docker.internal:host-gateway"

Then set provider base URL to http://host.docker.internal:11434/v1, wey be Ollama's OpenAI-compatible path, and put any non-empty string for API key field, because Ollama dey ignore am but OpenAI clients no go send empty one. Ollama must also listen beyond loopback before this go work, and that means OLLAMA_HOST=0.0.0.0:11434 for its systemd unit. Na the risky part be this: Ollama get no authentication, so open 11434 for public IP na free model server for anybody wey scan am first. Allow only Docker private range, sudo ufw allow from 172.16.0.0/12 to any port 11434 proto tcp, and deny the rest. Run Ollama for VPS cover model sizing, while the Ollama and vLLM comparison explain when Ollama no longer be the right server.

One more warning about local model, because e fit look like bug for Octop but e no be bug. Agents dey work by calling tools, and system prompt plus tool definitions plus history na large prompt. Ollama dey serve models with modest default context window, so the front of the prompt, wey tool definitions dey, go fall out of the window. The model go then stop calling tools or invent tools wey no exist. Raise num_ctx to 16k or 32k and choose model wey really good for function calling.

Self-hosted gateway. Put self-hosted LiteLLM gateway between Octop and everything else, and you go get one base URL, separate key per user, spend limits and one log. You fit also change the model behind am without editing anything for Octop.

Paid API. Quality dey best, but get honest tradeoff: conversation content go comot from your server go provider, and na this be most of wetin self-hosting dey meant to avoid. Put the key for docker/.env as OPENAI_API_KEY, and the Compose file already passes am through.

Any one wey you choose, the Compose file also carries OCTOP_LANGFUSE_ENABLED, LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY and LANGFUSE_BASE_URL, so you fit send traces to your own Langfuse instance and see wetin the agents really dey do instead of guessing from chat window.

Users, roles, and the shared agent library

Admin account wey come from first boot dey create and manage the other users. Every user get their own agents, workspace, and credentials. Na the token wey browser dey hold carry this separation. Alongside dem, shared pool of skills and sub-agents dey wey anybody fit use. Na this feature make running am useful for family: one person fit build good research agent once, and nobody else need build am again.

Make you careful with the tooling. Octop dey advertise tool approval and shell command guardrails, and both of dem dey work. But agent wey dey run shell commands dey run dem inside Octop container, with your data volume mounted. Guardrails reduce wetin careless prompt fit do. Dem no be sandbox boundary. So leave tool approval on for anybody wey you no go give shell access. If you dey compare this with other options, the roundup of self-hosted AI agents compare how each one dey handle that.

Project wey dey release new version this fast

ChartDays between Octop releases, v0.9.16 to v0.9.19 (repository tags, 7 August 2026)
The data behind this chart
[
  {
    "version": "v0.9.16",
    "days_since_previous_release": 2
  },
  {
    "version": "v0.9.17",
    "days_since_previous_release": 3
  },
  {
    "version": "v0.9.18",
    "days_since_previous_release": 1
  },
  {
    "version": "v0.9.19",
    "days_since_previous_release": 3
  }
]

Na tag dates from the repository be these, as of 7 August 2026. 4 tagged releases land within nine days. Gap fit short reach 1 day. v0.9.19 land 3 days after the tag before am. This release pattern dey show say the project dey active, but e no mean say you suppose run latest anyhow. Read the changes before you apply dem:

cd Octop
git fetch --tags
git tag --sort=-creatordate | head
NEW_TAG=$(git tag --sort=-creatordate | head -1)
git log --oneline "v0.9.19..$NEW_TAG"

Make backup first every time, because database migrations dey run when startup happen. If migration fail for project wey never reach 1.0, na you go need sort out the problem:

docker compose -f docker/docker-compose.yml stop
sudo tar czf octop-backup-$(date +%F).tgz -C /srv octop-data
docker compose -f docker/docker-compose.yml start

Then check out the new tag and rebuild with docker compose -f docker/docker-compose.yml up -d --build. If something go wrong, checking out the old tag and rebuilding go restore the code. But na only the tarball fit restore the database.

That tarball contain octop.db, config.json, the JWT signing secret and credential.txt, so e sensitive like the server itself. Set the file mode to 600 and keep one copy outside the server. For bigger installation, the project also ships docker/docker-compose.postgres.yml, wey dey run PostgreSQL with pgvector instead of SQLite.

Failure modes, wit di strings wey you go see

Health check no dey answer. curl http://127.0.0.1:8088/api/health dey hang or refuse. Read docker compose -f docker/docker-compose.yml logs -f octop. Container wey comot during first init usually no fit write to data directory, so check ownership of anything wey you set OCTOP_DATA to.

Dashboard dey load but chat dey hang. No error dey for page, and reply no dey ever come. Open browser console and look for failed connection to wss://octop.example.com/agents/.../chat/ws. Proxy no dey forward the upgrade. Add proxy_http_version 1.1 plus the Upgrade and Connection headers.

Whole reply dey show at once, several seconds late. Streaming dey work, but buffering dey on. Set proxy_buffering off.

bind: address already in use. Something don already hold 8088. sudo ss -tlnp | grep 8088 go show which one. You go also see this if you add second ports entry inside override file instead of editing the original one.

Correct password dey reject. Five wrong attempts trigger 900 second lockout. Wait make e expire instead of reinstalling.

New password for .env no take effect. Those credentials apply only during first init. Change am for dashboard.

Agent dey reply but e never run any tool. Almost always na local model problem: context window too small for tool definitions, or model no strong for function calling. Increase num_ctx and try model wey dem build for tool use.

FAQ

Octop na replacement for Open WebUI?

Na only if you need wetin e add. Open WebUI na chat interface wey dey front of a model, and e dey do that work well for one person or household wey trust each other. Octop add accounts with admin role, workspace and credentials for each user, plus library of specialist agents wey you fit switch, so several people fit share one server without sharing one history. If one account dey okay for you, Open WebUI na the simpler and much more mature choice.

Why I no suppose use Octop curl install script?

The script dey served from Tencent Cloud Object Storage bucket instead of the repository, so no git tag or commit cover am. You no fit compare wetin e dey do today with wetin e do last week, and piping am into bash go run am before you read am. E also install for the host with its own Python 3.12 environment, outside your package manager. Download am and read am first, or deploy with Docker Compose from a checked-out tag.

Octop fit use local model instead of paid API?

Yes. Octop dey speak OpenAI-compatible APIs and e ship with Ollama preset, so pointing am to http://host.docker.internal:11434/v1 go work once you add extra_hosts: ["host.docker.internal:host-gateway"] to the container and set OLLAMA_HOST=0.0.0.0:11434 for the host. Firewall port 11434 to Docker's address range, because Ollama no get authentication of its own. You go need raise Ollama's num_ctx to 16k or higher, because agent prompts with tool definitions dey overflow the default context window, and the model go then stop calling tools.

I need reverse proxy, or I fit open port 8088?

You need the proxy. Octop's shipped Compose file dey publish 8088 on every interface without TLS, so passwords and bearer tokens fit cross the internet as cleartext. Change the published port to 127.0.0.1:8088:8088 and put Caddy or nginx in front with certificate. With nginx, forward the WebSocket upgrade headers and set proxy_buffering off, or the page go load while chat silently no go respond.

Octop ready for production?

E still dey pre-1.0 and dey release several tagged releases every week as of August 2026, so treat am as promising, not settled yet. E fit work for family or small internal team if you pin exact tag, read commit log before every upgrade, and back up the data volume before every rebuild. No run am on latest, and no put customer data inside am yet.