Docker deployment
Xiajiao (虾饺) is designed so you don’t need Docker—npm start is enough. If you prefer containers, this path is fully supported.
When to use Docker
| Scenario | Docker? |
|---|---|
| Personal / local dev | ❌ faster with npm start |
| Shared team / reproducible env | ✅ |
| Co-deploy with other services | ✅ Docker Compose |
| CI/CD | ✅ build + push |
| Avoid local Node install | ✅ image includes runtime |
Quick start
git clone https://github.com/moziio/xiajiao.git
cd xiajiao
docker build -t xiajiao .
docker run -d -p 18800:18800 \
-v xiajiao-data:/app/data \
-v xiajiao-uploads:/app/public/uploads \
-e OWNER_KEY=my-secret \
--name xiajiao \
--restart unless-stopped \
xiajiaoOpen http://localhost:18800.
Image facts
| Property | Value |
|---|---|
| Base | node:22-slim |
| Build | npm ci --omit=dev |
| COPY strategy | Selective — only server/, public/, templates, presets, config |
| Size | Small (Debian slim, no dev deps) |
| Port | 18800 |
| Workdir | /app |
| NODE_ENV | production (set in image) |
| Volume | /app/data (single volume for all persistent data) |
Optimized build
The Dockerfile uses selective COPY instead of COPY . . — only production-necessary files enter the image. Combined with node:22-slim base and NODE_ENV=production, the result is a small, secure image.
Persistent volumes
One primary volume:
| Volume | Mount | Contents | Critical |
|---|---|---|---|
xiajiao-data | /app/data | SQLite, workspaces, SOUL templates, memory, HTTP tool definitions, custom tools | yes |
Uploads live under public/uploads/ (not under data/). If you need to persist uploads across container recreation, mount a separate volume for /app/public/uploads as shown in the quick start example above.
Mount volumes
Without volumes, removing the container deletes messages, Agents, and memory.
Bind mounts (host paths)
For direct file access (backup, edit SOUL.md):
mkdir -p /opt/xiajiao-data /opt/xiajiao-uploads
docker run -d -p 18800:18800 \
-v /opt/xiajiao-data:/app/data \
-v /opt/xiajiao-uploads:/app/public/uploads \
-e OWNER_KEY=my-secret \
--name xiajiao xiajiaoEdit /opt/xiajiao-data/workspace-xxx/SOUL.md on the host.
Docker Compose (recommended)
docker-compose.yml:
services:
xiajiao:
build: .
ports:
- "18800:18800"
volumes:
- ./volumes/data:/app/data
- ./volumes/uploads:/app/public/uploads
environment:
- OWNER_KEY=${OWNER_KEY:-admin}
- IM_PORT=18800
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:18800"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s.env:
OWNER_KEY=your-strong-passworddocker compose up -d
docker compose ps
docker compose logs -f xiajiao
docker compose downEnvironment variables
| Variable | Purpose | Default |
|---|---|---|
IM_PORT | Port | 18800 |
OWNER_KEY | Admin password | admin |
LLM_MODE | LLM mode | direct |
NODE_ENV | Node env | production |
Common tasks
Logs
docker logs -f xiajiao
docker logs --tail 100 xiajiaoShell in container
docker exec -it xiajiao sh
ls /app/data/
cat /app/data/workspace-xxx/SOUL.mdUpgrade
cd xiajiao
git pull
docker build -t xiajiao .
docker stop xiajiao
docker rm xiajiao
docker run -d -p 18800:18800 \
-v xiajiao-data:/app/data \
-v xiajiao-uploads:/app/public/uploads \
-e OWNER_KEY=my-secret \
--name xiajiao xiajiaoWith Compose:
git pull
docker compose up -d --buildBackup
docker cp xiajiao:/app/data ./backup-data
docker cp xiajiao:/app/public/uploads ./backup-uploads
docker run --rm -v xiajiao-data:/data -v $(pwd):/backup alpine \
tar czf /backup/xiajiao-data-backup.tar.gz -C /data .Nginx reverse proxy + HTTPS
docker-compose.yml excerpt (Xiajiao only exposes internally):
services:
xiajiao:
build: .
expose:
- "18800"
volumes:
- ./volumes/data:/app/data
- ./volumes/uploads:/app/public/uploads
environment:
- OWNER_KEY=${OWNER_KEY:-admin}
restart: unless-stopped
networks:
- web
networks:
web:
external: trueHost Nginx:
server {
listen 80;
server_name im.yourdomain.com;
location / {
proxy_pass http://xiajiao:18800;
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-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}WebSockets
Upgrade and Connection are required. Long proxy_read_timeout avoids dropping WS.
FAQ
Cannot reach container
docker ps— port mapping- Firewall:
sudo ufw allow 18800 docker logs xiajiao
Data gone after restart
Confirm mounts: docker inspect xiajiao → Mounts.
Permissions
sudo chown -R 1000:1000 /opt/xiajiao-dataDocker Compose + Ollama (fully private)
# docker-compose.ollama.yml
services:
xiajiao:
build: .
ports:
- "18800:18800"
volumes:
- ./volumes/data:/app/data
- ./volumes/uploads:/app/public/uploads
environment:
- OWNER_KEY=${OWNER_KEY:-admin}
restart: unless-stopped
depends_on:
ollama:
condition: service_healthy
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama-models:/root/.ollama
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
volumes:
ollama-models:docker compose -f docker-compose.ollama.yml up -d
docker exec ollama ollama pull qwen2.5
# In Xiajiao (虾饺) UI:
# API Base: http://ollama:11434/v1
# API Key: ollama
# Model: qwen2.5No GPU?
Remove deploy.resources; Ollama falls back to CPU (slower).
Logging
Structured log output
# JSON logs (ELK / Loki friendly)
docker logs xiajiao 2>&1 | jq '.'
docker logs --since "2026-03-19T00:00:00" xiajiao
# Limit log file size (see docker-compose.yml below)Limit log size in Compose:
services:
xiajiao:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"Production checklist
✅ OWNER_KEY changed (not admin)
✅ Volumes mounted (docker inspect)
✅ restart: unless-stopped
✅ Healthcheck configured
✅ Log rotation limits
✅ Firewall minimal exposure
✅ Public: Nginx + HTTPS
✅ Backups scheduledRelated docs
- Cloud deployment — public URL, HTTPS, DNS
- Run locally — without Docker
- Performance
- Security & privacy
- Troubleshooting
