Compare commits

..
Author SHA1 Message Date
Mauricio SiuandClaude Opus 5.5 3fb632006a fix(orbit): give storage its own generated domain
"storage-${domain}" pushes the first DNS label of Dokploy's generated
sslip.io hosts past 63 characters, so the storage domain never resolves
and browser uploads can't reach it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:59:42 -06:00
Mauricio SiuandClaude Opus 5.5 63b0eef948 fix(orbit): replace MinIO with SeaweedFS S3 storage
MinIO images on quay.io/Docker Hub are no longer publicly pullable, so the
deploy failed at image pull. Storage now runs SeaweedFS (S3 on port 9000,
credentials orbit/MINIO_PASSWORD) and the bucket service uses mc from the
orbit-bucket image to create orbit-uploads and set its CORS rule for the
app origin (browser presigned PUT uploads).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:19:35 -06:00
shashank agarwal e429bff0c2 fix(orbit): use tested versioned multi-platform images 2026-09-24 13:32:24 +05:30
shashank agarwal 6425052f98 fix: support generated storage passwords 2026-09-24 01:41:50 +05:30
shashank agarwal 62c2f0ed73 feat: add Orbit task management template 2026-09-24 01:20:22 +05:30
6 changed files with 292 additions and 5 deletions
+4 -5
View File
@@ -127,8 +127,7 @@ This document provides essential context for AI models interacting with this pro
- **NEVER include:** `ports` (use `expose` only), `container_name`, `networks` (Dokploy handles isolation)
- **ALWAYS include:** `restart: unless-stopped` or `restart: always`, persistent volumes
- **Service naming:** MUST match blueprint folder name exactly
- **Image source:** Prefer pinned Docker images from registries (`image: ghost:6-alpine`). When no official published images are available, use Docker Compose `build` with a Git context (e.g., `context: "https://github.com/..."#main`, see `blueprints/opendots` for an example building both `app` and `browser` targets from the upstream repository).
- **Example (with pinned image):**
- **Example:**
```yaml
version: "3.8"
services:
@@ -163,7 +162,7 @@ This document provides essential context for AI models interacting with this pro
- **Required fields:** `id`, `name`, `version`, `description`, `links` (with `github`), `logo`, `tags` (array)
- **Tags:** Lowercase strings (e.g., `["monitoring", "database"]`)
- **Version:** MUST match Docker image version in docker-compose.yml when using `image:`. When building from source (using `build:` with a Git context), the version should reflect the template's own version or the upstream project's version (e.g., `"latest"` for tracking `main` branch).
- **Version:** MUST match Docker image version in docker-compose.yml
- **Logo:** Filename only (e.g., `"ghost.jpeg"`), file must exist in blueprint folder
- **Frontend Development:**
@@ -184,8 +183,8 @@ This document provides essential context for AI models interacting with this pro
- **Dependencies:**
- When adding new templates, prefer pinned Docker images to specific versions when available
- Update meta.json with exact version matching Docker Compose image version (for `image:`), or the template/upstream version (for `build:` from source)
- When adding new templates, ensure Docker images are pinned to specific versions
- Update meta.json with exact version matching Docker Compose image version
- For frontend dependencies: Use `pnpm add <package>` in `app/` directory
- For root dependencies: Use `npm install <package>` in root directory
+230
View File
@@ -0,0 +1,230 @@
services:
postgres:
image: postgres:18-alpine@sha256:9a8afca54e7861fd90fab5fdf4c42477a6b1cb7d293595148e674e0a3181de15
environment:
POSTGRES_USER: orbit
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?required}
POSTGRES_DB: orbit
volumes:
- postgres:/var/lib/postgresql
healthcheck:
test:
- CMD-SHELL
- pg_isready -U orbit -d orbit
interval: 5s
timeout: 3s
retries: 20
restart: unless-stopped
redis:
image: redis:8-alpine@sha256:978f0e01593e65eed801f2402944efcd936d43b5027e4908a7897baf88ed6241
command:
- redis-server
- --appendonly
- 'yes'
volumes:
- redis:/data
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 3s
retries: 20
restart: unless-stopped
storage:
# SeaweedFS S3 gateway (MinIO images are no longer publicly pullable).
image: chrislusf/seaweedfs:4.48
entrypoint:
- /bin/sh
- -c
command:
- |
set -e
printf '{"identities":[{"name":"orbit","credentials":[{"accessKey":"orbit","secretKey":"%s"}],"actions":["Admin","Read","Write","List","Tagging"]}]}' "$$MINIO_PASSWORD" > /tmp/s3.json
exec weed server -dir=/data -ip.bind=0.0.0.0 -s3 -s3.port=9000 -s3.config=/tmp/s3.json
environment:
MINIO_PASSWORD: ${MINIO_PASSWORD:?required}
volumes:
- storage:/data
healthcheck:
test:
- CMD-SHELL
- wget -q -O /dev/null http://127.0.0.1:9000/healthz || exit 1
interval: 5s
timeout: 3s
retries: 30
restart: unless-stopped
expose:
- '9000'
bucket:
# Reuses the mc client from orbit-bucket to create the bucket and its CORS rule on SeaweedFS.
image: ghcr.io/noveum/orbit-bucket:2026.09.24
entrypoint:
- /bin/sh
- -c
command:
- |
set -eu
until mc alias set orbit http://storage:9000 orbit "$$MINIO_PASSWORD" --api S3v4; do sleep 2; done
until mc mb --ignore-existing orbit/orbit-uploads; do sleep 2; done
printf '<CORSConfiguration><CORSRule><AllowedOrigin>%s</AllowedOrigin><AllowedMethod>GET</AllowedMethod><AllowedMethod>PUT</AllowedMethod><AllowedMethod>HEAD</AllowedMethod><AllowedHeader>*</AllowedHeader><ExposeHeader>ETag</ExposeHeader><MaxAgeSeconds>3600</MaxAgeSeconds></CORSRule></CORSConfiguration>' "$$ORBIT_APP_URL" > /tmp/cors.xml
until mc cors set orbit/orbit-uploads /tmp/cors.xml; do sleep 2; done
touch /tmp/initialized
exec tail -f /dev/null
environment:
MINIO_PASSWORD: ${MINIO_PASSWORD:?required}
ORBIT_APP_URL: ${ORBIT_APP_URL:?required}
depends_on:
storage:
condition: service_healthy
restart: unless-stopped
healthcheck:
test:
- CMD-SHELL
- test -f /tmp/initialized
interval: 5s
timeout: 3s
retries: 30
web:
image: ghcr.io/noveum/orbit-runtime:2026.09.24
environment:
DATABASE_URL: postgres://orbit:${POSTGRES_PASSWORD:?required}@postgres:5432/orbit
DIRECT_URL: postgres://orbit:${POSTGRES_PASSWORD:?required}@postgres:5432/orbit
DATABASE_PREPARED_STATEMENTS: 'true'
REDIS_URL: redis://redis:6379
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?required}
BETTER_AUTH_URL: ${ORBIT_APP_URL:?required}
NEXT_PUBLIC_APP_URL: ${ORBIT_APP_URL:?required}
ORBIT_PASSWORD_AUTH: ${ORBIT_PASSWORD_AUTH:-true}
ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-}
RESEND_API_KEY: ${RESEND_API_KEY:-}
EMAIL_FROM: ${EMAIL_FROM:-Orbit <auth@orbit.local>}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-}
GITHUB_APP_SLUG: ${GITHUB_APP_SLUG:-}
GITHUB_APP_ID: ${GITHUB_APP_ID:-}
GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY:-}
GITHUB_APP_CLIENT_ID: ${GITHUB_APP_CLIENT_ID:-}
GITHUB_APP_CLIENT_SECRET: ${GITHUB_APP_CLIENT_SECRET:-}
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:-}
SLACK_ENABLED: ${SLACK_ENABLED:-false}
SLACK_CLIENT_ID: ${SLACK_CLIENT_ID:-}
SLACK_CLIENT_SECRET: ${SLACK_CLIENT_SECRET:-}
SLACK_SIGNING_SECRET: ${SLACK_SIGNING_SECRET:-}
SLACK_APP_ID: ${SLACK_APP_ID:-}
CRON_SECRET: ${CRON_SECRET:-}
ORBIT_SELF_HOSTED: 'true'
S3_ENDPOINT: ${ORBIT_STORAGE_URL:?required}
S3_REGION: us-east-1
S3_BUCKET: orbit-uploads
S3_ACCESS_KEY_ID: orbit
S3_SECRET_ACCESS_KEY: ${MINIO_PASSWORD:?required}
NEXT_TELEMETRY_DISABLED: '1'
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:3000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))
interval: 10s
timeout: 5s
start_period: 30s
retries: 5
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
bucket:
condition: service_healthy
restart: unless-stopped
realtime:
image: ghcr.io/noveum/orbit-runtime:2026.09.24
command:
- node
- apps/web/realtime.mjs
environment:
DATABASE_URL: postgres://orbit:${POSTGRES_PASSWORD:?required}@postgres:5432/orbit
DIRECT_URL: postgres://orbit:${POSTGRES_PASSWORD:?required}@postgres:5432/orbit
DATABASE_PREPARED_STATEMENTS: 'true'
REDIS_URL: redis://redis:6379
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?required}
BETTER_AUTH_URL: ${ORBIT_APP_URL:?required}
NEXT_PUBLIC_APP_URL: ${ORBIT_APP_URL:?required}
ORBIT_PASSWORD_AUTH: ${ORBIT_PASSWORD_AUTH:-true}
ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-}
RESEND_API_KEY: ${RESEND_API_KEY:-}
EMAIL_FROM: ${EMAIL_FROM:-Orbit <auth@orbit.local>}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-}
GITHUB_APP_SLUG: ${GITHUB_APP_SLUG:-}
GITHUB_APP_ID: ${GITHUB_APP_ID:-}
GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY:-}
GITHUB_APP_CLIENT_ID: ${GITHUB_APP_CLIENT_ID:-}
GITHUB_APP_CLIENT_SECRET: ${GITHUB_APP_CLIENT_SECRET:-}
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:-}
SLACK_ENABLED: ${SLACK_ENABLED:-false}
SLACK_CLIENT_ID: ${SLACK_CLIENT_ID:-}
SLACK_CLIENT_SECRET: ${SLACK_CLIENT_SECRET:-}
SLACK_SIGNING_SECRET: ${SLACK_SIGNING_SECRET:-}
SLACK_APP_ID: ${SLACK_APP_ID:-}
CRON_SECRET: ${CRON_SECRET:-}
ORBIT_SELF_HOSTED: 'true'
S3_ENDPOINT: ${ORBIT_STORAGE_URL:?required}
S3_REGION: us-east-1
S3_BUCKET: orbit-uploads
S3_ACCESS_KEY_ID: orbit
S3_SECRET_ACCESS_KEY: ${MINIO_PASSWORD:?required}
NEXT_TELEMETRY_DISABLED: '1'
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:3100/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))
interval: 10s
timeout: 5s
retries: 5
depends_on:
web:
condition: service_healthy
bucket:
condition: service_healthy
restart: unless-stopped
scheduler:
image: ghcr.io/noveum/orbit-runtime:2026.09.24
command:
- node
- apps/web/scheduler.mjs
environment:
CRON_SECRET: ${CRON_SECRET:?required}
ORBIT_INTERNAL_URL: http://web:3000
stop_grace_period: 310s
depends_on:
web:
condition: service_healthy
restart: unless-stopped
orbit:
image: ghcr.io/noveum/orbit-gateway:2026.09.24
environment:
ORBIT_TRUSTED_PROXIES: ${ORBIT_TRUSTED_PROXIES:-private_ranges}
depends_on:
web:
condition: service_healthy
realtime:
condition: service_healthy
scheduler:
condition: service_started
restart: unless-stopped
expose:
- '3000'
volumes:
postgres: null
redis: null
storage: null
version: '3.8'
+13
View File
@@ -0,0 +1,13 @@
# Orbit by Noveum
Deploy, then enable HTTPS with a valid certificate for both generated domains
in Dokploy's Domains tab. Open the Orbit HTTPS domain and create your account
and workspace. Both the app and storage domains require working HTTPS. Database migrations,
bucket creation and the storage CORS allowlist are configured automatically.
PostgreSQL, Redis and SeaweedFS (S3 storage) use persistent volumes. Preserve generated secrets
and back up PostgreSQL and the SeaweedFS storage volume before upgrades. Run one scheduler instance.
Configure Resend and EMAIL_FROM on web to send invitations and password recovery.
Configure ALLOWED_EMAIL_DOMAINS to restrict signup to your team.
Hosted MCP: https://orbit.noveum.ai/mcp
Self-hosted MCP: your Orbit HTTPS domain followed by /mcp.
+18
View File
@@ -0,0 +1,18 @@
{
"id": "orbit",
"name": "Orbit by Noveum",
"version": "2026.09.24",
"description": "Open-source task management with issues, projects, sprints, docs, files, realtime collaboration and an OAuth MCP endpoint.",
"logo": "orbit.png",
"links": {
"github": "https://github.com/Noveum/orbit",
"website": "https://orbit.noveum.ai",
"docs": "https://github.com/Noveum/orbit/blob/containers-2026.09.24/docs/container-catalogs.md"
},
"tags": [
"productivity",
"project-management",
"collaboration",
"mcp"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+27
View File
@@ -0,0 +1,27 @@
[variables]
main_domain = "${domain}"
storage_domain = "${domain}"
postgres_password = "${password:48}"
minio_password = "${password:48}"
auth_secret = "${password:64}"
cron_secret = "${password:64}"
[config]
env = [
"ORBIT_APP_URL=https://${main_domain}",
"ORBIT_STORAGE_URL=https://${storage_domain}",
"POSTGRES_PASSWORD=${postgres_password}",
"MINIO_PASSWORD=${minio_password}",
"BETTER_AUTH_SECRET=${auth_secret}",
"CRON_SECRET=${cron_secret}",
]
[[config.domains]]
serviceName = "orbit"
port = 3000
host = "${main_domain}"
[[config.domains]]
serviceName = "storage"
port = 9000
host = "${storage_domain}"