Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fb632006a | ||
|
|
63b0eef948 | ||
|
|
e429bff0c2 | ||
|
|
6425052f98 | ||
|
|
62c2f0ed73 | ||
|
|
830d6bbc8d | ||
|
|
b87d9a9a8a | ||
|
|
a9aa64ee51 | ||
|
|
8ac27a0f89 | ||
|
|
614e4fef12 | ||
|
|
c784f9b9c8 | ||
|
|
698ab85f6d | ||
|
|
6284bbc74f | ||
|
|
7b1cf99803 | ||
|
|
1aa2d74f4e | ||
|
|
b35349963e | ||
|
|
127b607d76 | ||
|
|
c974384957 | ||
|
|
b05ce42a12 | ||
|
|
f29a0b7340 | ||
|
|
b5b37264a4 | ||
|
|
39ce5301b4 | ||
|
|
d5d374ad72 |
@@ -0,0 +1,12 @@
|
||||
# BlogFactory on Dokploy
|
||||
|
||||
Dokploy creates independent database, storage, JWT, encryption, and scheduler secrets during import. Only the `web` service receives a public domain; the API, scheduler, PostgreSQL, and MinIO remain private.
|
||||
|
||||
## First administrator
|
||||
|
||||
1. Open the Compose service's environment settings and copy the generated `ADMIN_EMAILS` value.
|
||||
2. Open the generated BlogFactory domain and register with that exact email address.
|
||||
3. Return to the Compose environment settings, change `BLOGFACTORY_ALLOW_SIGNUP` to `false`, and redeploy.
|
||||
4. Add OpenRouter, CMS, or Google Search Console credentials from BlogFactory only when you need those optional integrations.
|
||||
|
||||
BlogFactory sends reviewed content to connected CMS providers as drafts. The template does not grant live-publish or delete authority.
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect width="64" height="64" rx="8" fill="#fff"/>
|
||||
<rect x="1" y="1" width="62" height="62" rx="7" fill="none" stroke="#d7d8d4" stroke-width="2"/>
|
||||
<circle cx="50" cy="14" r="5" fill="#ff5a1f"/>
|
||||
<rect x="13" y="31" width="10" height="18" fill="#202224"/>
|
||||
<rect x="27" y="20" width="10" height="29" fill="#202224"/>
|
||||
<rect x="41" y="26" width="10" height="23" fill="#202224"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
@@ -0,0 +1,102 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.6-alpine3.22
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: blogfactory
|
||||
POSTGRES_USER: blogfactory
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U blogfactory -d blogfactory"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||
restart: unless-stopped
|
||||
command: server /data --console-address :9001
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
api:
|
||||
image: ghcr.io/blogfactoryhq/blogfactory-api:v0.1.0
|
||||
restart: unless-stopped
|
||||
command: >-
|
||||
sh -c "bun run src/init-s3-bucket.ts && bun run src/db/migrate.ts && exec bun run src/index.ts"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
expose:
|
||||
- 3000
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
BLOGFACTORY_SELF_HOSTED: "true"
|
||||
DATABASE_URL: postgresql://blogfactory:${POSTGRES_PASSWORD}@postgres:5432/blogfactory
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
API_KEY_ENCRYPTION_SECRET: ${API_KEY_ENCRYPTION_SECRET}
|
||||
ADMIN_EMAILS: ${ADMIN_EMAILS}
|
||||
BLOGFACTORY_ALLOW_SIGNUP: ${BLOGFACTORY_ALLOW_SIGNUP}
|
||||
CRON_SECRET: ${CRON_SECRET}
|
||||
WEB_APP_URL: ${BLOGFACTORY_URL}
|
||||
MCP_APP_URL: http://web/mcp-review.html
|
||||
MCP_ALLOWED_ORIGINS: ${BLOGFACTORY_URL}
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_ACCESS_KEY_ID: ${MINIO_ROOT_USER}
|
||||
S3_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD}
|
||||
S3_BUCKET: blogfactory
|
||||
S3_REGION: us-east-1
|
||||
healthcheck:
|
||||
test: ["CMD", "bun", "-e", "fetch('http://localhost:3000/api/ready').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
web:
|
||||
image: ghcr.io/blogfactoryhq/blogfactory-web:v0.1.0
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
expose:
|
||||
- 80
|
||||
environment:
|
||||
API_UPSTREAM: http://api:3000
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "nginx -t >/dev/null 2>&1 && wget -q -O /dev/null http://api:3000/api/ready"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
scheduler:
|
||||
image: curlimages/curl:8.15.0
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
CRON_SECRET: ${CRON_SECRET}
|
||||
CRON_INTERVAL_SECONDS: 21600
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- >-
|
||||
while true; do curl --fail --silent --show-error --output /dev/null -H "Authorization: Bearer $${CRON_SECRET}" http://api:3000/api/cron/drain && echo "Scheduler drain completed"; sleep "$${CRON_INTERVAL_SECONDS}"; done
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
minio-data:
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "blogfactory",
|
||||
"name": "BlogFactory",
|
||||
"version": "v0.1.0",
|
||||
"description": "Self-hosted control plane for AI-assisted content operations and human-approved CMS draft delivery.",
|
||||
"logo": "blogfactory.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/BlogFactoryHQ/blogfactory",
|
||||
"website": "https://blogfactory.io/",
|
||||
"docs": "https://github.com/BlogFactoryHQ/blogfactory/blob/main/docs/self-hosting.md"
|
||||
},
|
||||
"tags": ["ai", "automation", "content-management"]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_password = "${password:48}"
|
||||
minio_root_user = "blogfactory"
|
||||
minio_root_password = "${password:48}"
|
||||
jwt_secret = "${base64:48}"
|
||||
encryption_secret = "${base64:48}"
|
||||
cron_secret = "${base64:48}"
|
||||
admin_email = "${email}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"MINIO_ROOT_USER=${minio_root_user}",
|
||||
"MINIO_ROOT_PASSWORD=${minio_root_password}",
|
||||
"JWT_SECRET=${jwt_secret}",
|
||||
"API_KEY_ENCRYPTION_SECRET=${encryption_secret}",
|
||||
"CRON_SECRET=${cron_secret}",
|
||||
"ADMIN_EMAILS=${admin_email}",
|
||||
"BLOGFACTORY_ALLOW_SIGNUP=true",
|
||||
"BLOGFACTORY_URL=https://${main_domain}",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "web"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,37 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
falkordb:
|
||||
image: falkordb/falkordb:v4.20.4-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- REDIS_ARGS=--requirepass ${FALKORDB_PASSWORD} --appendonly yes
|
||||
- FALKORDB_ARGS=MAX_QUEUED_QUERIES 25 TIMEOUT 1000 RESULTSET_SIZE 10000 IMPORT_FOLDER /app/.data/import
|
||||
- FALKORDB_PASSWORD=${FALKORDB_PASSWORD}
|
||||
- AUTH_URL=${AUTH_URL}
|
||||
- AUTH_SECRET=${AUTH_SECRET}
|
||||
- NEXTAUTH_URL=${AUTH_URL}
|
||||
- NEXTAUTH_SECRET=${AUTH_SECRET}
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
||||
- ALLOWED_ORIGINS=${AUTH_URL}
|
||||
- TRUST_PROXY_HEADERS=true
|
||||
- CSV_STORAGE=local
|
||||
- CSV_LOCAL_TEMP_DIR=/app/.data/import
|
||||
- CSV_LOCAL_LOAD_URI_MODE=file
|
||||
- API_TOKEN_STORAGE_PATH=/app/.data/api_tokens.json
|
||||
volumes:
|
||||
- falkordb_data:/var/lib/falkordb/data
|
||||
- falkordb_browser_data:/app/.data
|
||||
expose:
|
||||
- 3000
|
||||
- 6379
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "REDISCLI_AUTH=$$FALKORDB_PASSWORD redis-cli ping | grep PONG"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
falkordb_data:
|
||||
falkordb_browser_data:
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg width="508" height="508" viewBox="0 0 508 508" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1764_80)">
|
||||
<rect x="2" width="508" height="508" rx="63.5" fill="#191919"/>
|
||||
<path d="M459.448 247.263C459.06 249.273 458.295 251.265 457.465 253.036C456.174 255.775 454.477 258.357 452.439 260.598C447.809 265.671 441.271 268.253 434.576 269.608C427.88 270.964 407.573 270.088 399.089 268.041C377.481 262.821 355.182 262.941 341.966 268.05C333.583 271.296 330.651 273.15 326.224 276.968C308.711 292.027 300.872 328.455 293.919 349.897C289.188 364.56 284.549 379.823 273.925 391.378C257.565 409.149 230.332 414 205.635 414H97.1546C95.7989 414 94.7752 412.773 95.0427 411.436L97.588 398.35C99.6538 387.689 109.005 379.998 119.869 379.998H192.706C213.732 379.998 236.234 377.056 247.891 360.963C254.43 351.916 256.311 340.711 258.985 330.051C268.346 292.728 271.454 258.219 308.822 243.5C297.811 236.621 289.806 225.499 287.021 213.27C281.902 190.694 294.011 167.962 295.136 144.897C295.634 135.075 293.319 123.999 284.697 118.549C278.656 114.722 270.91 114.611 263.652 114.611C236.409 114.639 219.182 114.657 187.504 114.676H180.145C165.897 114.685 155.199 101.691 157.919 87.7101L158.685 83.7446C158.879 82.7209 159.745 82.0016 160.778 82.0016C177.637 82.0016 241.602 82.103 273.695 82.0016C292.222 81.9555 313.036 82.8224 325.579 95.7242C329.738 100.013 332.394 104.992 333.962 110.387C334.035 110.6 334.091 110.83 334.155 111.051C337.752 124.267 334.921 139.852 331.296 153.621C326.63 171.531 321.632 192.345 331.628 209.461C335.603 216.258 341.828 221.293 349.224 223.959C353.356 225.453 355.126 226.2 359.516 226.163C361.711 226.144 363.897 225.923 366.064 225.536C370.38 224.779 375.074 223.728 378.966 222.963C380.829 222.594 382.701 222.262 384.591 221.994C389.719 221.275 395.206 221.137 400.426 222.179C404.253 222.944 407.988 224.355 411.188 226.605C414.591 229.003 416.602 231.982 418.391 235.671C419.396 237.737 420.641 239.83 422.67 240.909C426.303 242.846 431.938 241.545 435.848 241.232C439.915 240.909 443.955 240.273 448.012 239.802C449.986 239.572 451.959 239.369 453.942 239.277C455.501 239.203 457.198 239.406 458.369 240.559C459.208 241.388 459.605 242.587 459.697 243.768C459.78 244.911 459.678 246.083 459.457 247.254L459.448 247.263Z" fill="url(#paint0_linear_1764_80)"/>
|
||||
<path d="M182.763 183.28L172.627 235.164C172.36 236.556 173.421 237.848 174.841 237.848H229.159C230.58 237.848 231.64 239.139 231.373 240.531L228.956 253.055C226.909 263.66 217.622 271.333 206.823 271.333H167.509C166.43 271.333 165.499 272.099 165.296 273.159L154.663 327.847C152.606 338.443 143.319 346.088 132.529 346.088H110.525C109.105 346.088 108.045 344.797 108.312 343.405L145.902 149.794C146.104 148.734 147.036 147.968 148.115 147.968H254.096C255.516 147.968 256.577 149.259 256.309 150.652L253.893 163.176C251.846 173.781 242.559 181.454 231.76 181.454H184.985C183.906 181.454 182.975 182.219 182.772 183.28H182.763Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1764_80" x1="459.787" y1="82.0147" x2="70.7574" y2="381.599" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#7466FF"/>
|
||||
<stop offset="0.5" stop-color="#FF66B3"/>
|
||||
<stop offset="1" stop-color="#FF804D"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_1764_80">
|
||||
<rect width="508" height="508" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,4 @@
|
||||
1. Open the generated domain to access FalkorDB Browser.
|
||||
2. Create a connection using `localhost` as the host and `6379` as the port.
|
||||
3. Leave the username empty and use the generated `FALKORDB_PASSWORD` from the Dokploy environment settings.
|
||||
4. Keep port `6379` private unless remote database clients need direct access. The browser interface can access it inside the container.
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"id": "falkordb",
|
||||
"name": "FalkorDB",
|
||||
"version": "4.20.4",
|
||||
"description": "A high-performance graph database for knowledge graphs, GraphRAG, agent memory, fraud detection and other relationship-heavy workloads. Includes the FalkorDB Browser interface.",
|
||||
"logo": "falkordb.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/FalkorDB/FalkorDB",
|
||||
"website": "https://www.falkordb.com/",
|
||||
"docs": "https://docs.falkordb.com/"
|
||||
},
|
||||
"tags": [
|
||||
"database",
|
||||
"graph-database",
|
||||
"knowledge-graph",
|
||||
"graphrag",
|
||||
"ai",
|
||||
"redis"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
falkordb_url = "https://${main_domain}"
|
||||
falkordb_password = "${hash:32}"
|
||||
auth_secret = "${base64:48}"
|
||||
encryption_key = "${hash:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"FALKORDB_PASSWORD=${falkordb_password}",
|
||||
"AUTH_URL=${falkordb_url}",
|
||||
"AUTH_SECRET=${auth_secret}",
|
||||
"ENCRYPTION_KEY=${encryption_key}",
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "falkordb"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,101 @@
|
||||
# Firecrawl
|
||||
|
||||
This template deploys Firecrawl `2.11.230` with authenticated API access, persistent infrastructure, browser rendering, and interactive Swagger documentation.
|
||||
|
||||
## First deployment
|
||||
|
||||
1. Create the template and keep the generated environment values unchanged.
|
||||
2. Deploy the Compose service.
|
||||
3. Wait for `firecrawl-db-seed` to finish and for `gateway` and `firecrawl` to become healthy. The first start can take several minutes.
|
||||
4. Open `https://<your-domain>/docs/` for the interactive API documentation.
|
||||
|
||||
Dokploy generates the client credential in the Environment tab:
|
||||
|
||||
```text
|
||||
FIRECRAWL_API_KEY=fc-...
|
||||
```
|
||||
|
||||
Treat this value as a secret. The template provisions it in Firecrawl's PostgreSQL authentication database automatically.
|
||||
|
||||
## Test the API
|
||||
|
||||
```bash
|
||||
curl -X POST "https://<your-domain>/v2/scrape" \
|
||||
-H "Authorization: Bearer <FIRECRAWL_API_KEY>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"url":"https://docs.dokploy.com","formats":[{"type":"markdown"}]}'
|
||||
```
|
||||
|
||||
The OpenAPI document is available at:
|
||||
|
||||
```text
|
||||
https://<your-domain>/openapi.json
|
||||
```
|
||||
|
||||
## Included services
|
||||
|
||||
- `gateway`: public same-origin API and documentation gateway.
|
||||
- `firecrawl`: API plus queue, extract, indexing, NuQ, and reconciliation workers.
|
||||
- `playwright-service`: browser-rendered scraping.
|
||||
- `firecrawl-db`: authentication, request, crawl, monitoring, and application data.
|
||||
- `nuq-postgres`: durable NuQ job storage.
|
||||
- `redis`: queues, caching, and rate-limit state.
|
||||
- `rabbitmq`: extract-job transport.
|
||||
- `swagger-ui`: interactive API documentation.
|
||||
|
||||
Only the gateway is assigned a public domain. PostgreSQL, Redis, RabbitMQ, Playwright, and worker ports remain private.
|
||||
|
||||
## Capabilities
|
||||
|
||||
Scrape, batch scrape, crawl, map, parsing, browser sessions, monitoring, change tracking, and the normal non-AI API paths work without an external model provider.
|
||||
|
||||
AI-backed extraction, JSON generation, agent features, and summaries require one of these configurations in the Environment tab:
|
||||
|
||||
- `OPENAI_API_KEY`, optionally with `OPENAI_BASE_URL`
|
||||
- `OPENROUTER_API_KEY`
|
||||
- `OLLAMA_BASE_URL`
|
||||
|
||||
Set `MODEL_NAME` and `MODEL_EMBEDDING_NAME` when the provider requires explicit model names.
|
||||
|
||||
Search integrations can be configured with `SEARCHAPI_API_KEY` or `SEARXNG_ENDPOINT`. Proxy variables and LlamaParse are optional.
|
||||
|
||||
## MCP clients
|
||||
|
||||
The Firecrawl API is not itself an MCP transport. Connect the official Firecrawl MCP adapter to this deployment:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"firecrawl-selfhost": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "firecrawl-mcp"],
|
||||
"env": {
|
||||
"FIRECRAWL_API_URL": "https://<your-domain>",
|
||||
"FIRECRAWL_API_KEY": "<FIRECRAWL_API_KEY>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Persistence and backups
|
||||
|
||||
The following named volumes contain persistent state:
|
||||
|
||||
- `firecrawl-db-data`
|
||||
- `nuq-postgres-data`
|
||||
- `redis-data`
|
||||
- `rabbitmq-data`
|
||||
|
||||
Back up both PostgreSQL volumes before upgrading. Redis and RabbitMQ backups are also recommended when queued jobs must survive disaster recovery.
|
||||
|
||||
Database initialization scripts run only when `firecrawl-db-data` is empty. A newer Firecrawl image may require schema or RPC changes, so do not replace the pinned image with `latest` without reviewing migrations and testing a restored backup.
|
||||
|
||||
## Security notes
|
||||
|
||||
- Keep `ALLOW_LOCAL_WEBHOOKS=false` unless private-network webhook access is intentional.
|
||||
- Keep the generated database, Redis, RabbitMQ, and Bull secrets private.
|
||||
- Use HTTPS for the public domain.
|
||||
- The generated API key receives unrestricted self-host rate-limit overrides; protect it like an administrator credential.
|
||||
- Changing `FIRECRAWL_API_KEY` after deployment provisions another key and does not automatically revoke the old one.
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"id": "firecrawl",
|
||||
"name": "Firecrawl",
|
||||
"version": "latest",
|
||||
"description": "Firecrawl is an API service that takes a URL, crawls it, and converts it into clean markdown or structured data. It can crawl all accessible subpages and provide clean data for each.",
|
||||
"version": "2.11.230",
|
||||
"description": "Production-ready, authenticated Firecrawl with persistent queues, browser rendering, a generated API key, and interactive OpenAPI documentation.",
|
||||
"logo": "firecrawl.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/firecrawl/firecrawl",
|
||||
"website": "https://firecrawl.dev",
|
||||
"docs": "https://github.com/firecrawl/firecrawl"
|
||||
"docs": "https://docs.firecrawl.dev/contributing/self-host"
|
||||
},
|
||||
"tags": [
|
||||
"api",
|
||||
"crawler",
|
||||
"scraping",
|
||||
"data-extraction",
|
||||
"llm"
|
||||
"llm",
|
||||
"automation",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,65 +1,61 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
openai_api_key = "${OPENAI_API_KEY}"
|
||||
openai_base_url = "${OPENAI_BASE_URL}"
|
||||
ollama_base_url = "${OLLAMA_BASE_URL}"
|
||||
model_name = "${MODEL_NAME}"
|
||||
model_embedding_name = "${MODEL_EMBEDDING_NAME}"
|
||||
proxy_server = "${PROXY_SERVER}"
|
||||
proxy_username = "${PROXY_USERNAME}"
|
||||
proxy_password = "${PROXY_PASSWORD}"
|
||||
searxng_endpoint = "${SEARXNG_ENDPOINT}"
|
||||
searxng_engines = "${SEARXNG_ENGINES}"
|
||||
searxng_categories = "${SEARXNG_CATEGORIES}"
|
||||
supabase_anon_token = "${SUPABASE_ANON_TOKEN}"
|
||||
supabase_url = "${SUPABASE_URL}"
|
||||
supabase_service_token = "${SUPABASE_SERVICE_TOKEN}"
|
||||
test_api_key = "${TEST_API_KEY}"
|
||||
bull_auth_key = "${password:32}"
|
||||
llamaparse_api_key = "${LLAMAPARSE_API_KEY}"
|
||||
slack_webhook_url = "${SLACK_WEBHOOK_URL}"
|
||||
posthog_api_key = "${POSTHOG_API_KEY}"
|
||||
posthog_host = "${POSTHOG_HOST}"
|
||||
max_cpu = "${MAX_CPU}"
|
||||
max_ram = "${MAX_RAM}"
|
||||
app_postgres_password = "${hash:32}"
|
||||
nuq_postgres_password = "${hash:32}"
|
||||
redis_password = "${hash:32}"
|
||||
rabbitmq_password = "${hash:32}"
|
||||
bull_auth_key = "${hash:32}"
|
||||
firecrawl_org_id = "${uuid}"
|
||||
firecrawl_team_id = "${uuid}"
|
||||
# Dokploy's real hash helper returns exactly N hex characters. Force the UUID
|
||||
# version and variant nibbles so Firecrawl's strict API-key parser accepts it.
|
||||
firecrawl_api_key_a = "${hash:12}"
|
||||
firecrawl_api_key_b = "${hash:3}"
|
||||
firecrawl_api_key_c = "${hash:15}"
|
||||
firecrawl_api_key = "fc-${firecrawl_api_key_a}4${firecrawl_api_key_b}8${firecrawl_api_key_c}"
|
||||
self_hosted_webhook_hmac_secret = "${hash:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"PORT=3002",
|
||||
"HOST=0.0.0.0",
|
||||
"USE_DB_AUTHENTICATION=false",
|
||||
"APP_POSTGRES_PASSWORD=${app_postgres_password}",
|
||||
"NUQ_POSTGRES_PASSWORD=${nuq_postgres_password}",
|
||||
"REDIS_PASSWORD=${redis_password}",
|
||||
"RABBITMQ_USER=firecrawl",
|
||||
"RABBITMQ_PASSWORD=${rabbitmq_password}",
|
||||
"BULL_AUTH_KEY=${bull_auth_key}",
|
||||
"PLAYWRIGHT_MICROSERVICE_URL=http://playwright-service:3000/scrape",
|
||||
"REDIS_URL=redis://redis:6379",
|
||||
"REDIS_RATE_LIMIT_URL=redis://redis:6379",
|
||||
"OPENAI_API_KEY=${openai_api_key}",
|
||||
"OPENAI_BASE_URL=${openai_base_url}",
|
||||
"OLLAMA_BASE_URL=${ollama_base_url}",
|
||||
"MODEL_NAME=${model_name}",
|
||||
"MODEL_EMBEDDING_NAME=${model_embedding_name}",
|
||||
"PROXY_SERVER=${proxy_server}",
|
||||
"PROXY_USERNAME=${proxy_username}",
|
||||
"PROXY_PASSWORD=${proxy_password}",
|
||||
"SEARXNG_ENDPOINT=${searxng_endpoint}",
|
||||
"SEARXNG_ENGINES=${searxng_engines}",
|
||||
"SEARXNG_CATEGORIES=${searxng_categories}",
|
||||
"SUPABASE_ANON_TOKEN=${supabase_anon_token}",
|
||||
"SUPABASE_URL=${supabase_url}",
|
||||
"SUPABASE_SERVICE_TOKEN=${supabase_service_token}",
|
||||
"TEST_API_KEY=${test_api_key}",
|
||||
"LLAMAPARSE_API_KEY=${llamaparse_api_key}",
|
||||
"SLACK_WEBHOOK_URL=${slack_webhook_url}",
|
||||
"POSTHOG_API_KEY=${posthog_api_key}",
|
||||
"POSTHOG_HOST=${posthog_host}",
|
||||
"MAX_CPU=0.8",
|
||||
"MAX_RAM=0.8"
|
||||
"FIRECRAWL_ORG_ID=${firecrawl_org_id}",
|
||||
"FIRECRAWL_TEAM_ID=${firecrawl_team_id}",
|
||||
"FIRECRAWL_API_KEY=${firecrawl_api_key}",
|
||||
"NUM_WORKERS_PER_QUEUE=8",
|
||||
"CRAWL_CONCURRENT_REQUESTS=10",
|
||||
"MAX_CONCURRENT_JOBS=5",
|
||||
"BROWSER_POOL_SIZE=5",
|
||||
"LOGGING_LEVEL=INFO",
|
||||
"DISABLE_ENGPICKER=true",
|
||||
"ALLOW_LOCAL_WEBHOOKS=false",
|
||||
"BLOCK_MEDIA=true",
|
||||
"OPENAI_API_KEY=",
|
||||
"OPENAI_BASE_URL=",
|
||||
"OPENROUTER_API_KEY=",
|
||||
"OLLAMA_BASE_URL=",
|
||||
"MODEL_NAME=",
|
||||
"MODEL_EMBEDDING_NAME=",
|
||||
"SEARCHAPI_API_KEY=",
|
||||
"SEARXNG_ENDPOINT=",
|
||||
"SEARXNG_ENGINES=",
|
||||
"SEARXNG_CATEGORIES=",
|
||||
"LLAMAPARSE_API_KEY=",
|
||||
"SLACK_WEBHOOK_URL=",
|
||||
"PROXY_SERVER=",
|
||||
"PROXY_USERNAME=",
|
||||
"PROXY_PASSWORD=",
|
||||
"SELF_HOSTED_WEBHOOK_URL=",
|
||||
"SELF_HOSTED_WEBHOOK_HMAC_SECRET=${self_hosted_webhook_hmac_secret}"
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "api"
|
||||
port = 3002
|
||||
serviceName = "gateway"
|
||||
port = 8080
|
||||
host = "${main_domain}"
|
||||
path = "/"
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
kotonoba:
|
||||
image: ghcr.io/trixtylab/kotonoba:1.0.19
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DB_PATH: ${DB_PATH}
|
||||
UPLOAD_DIR: ${UPLOAD_DIR}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
SITE_URL: ${SITE_URL}
|
||||
ADMIN_DOMAIN: ${ADMIN_DOMAIN}
|
||||
volumes:
|
||||
- blog_data:/app/data
|
||||
|
||||
volumes:
|
||||
blog_data: {}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="100%" height="100%">
|
||||
<defs>
|
||||
<!-- Primary Accent Gradient (Electric Indigo to Violet) -->
|
||||
<linearGradient id="iconAccent" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#818CF8" />
|
||||
<stop offset="100%" stop-color="#4F46E5" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Cyan Highlight Gradient -->
|
||||
<linearGradient id="iconCyan" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#38BDF8" />
|
||||
<stop offset="100%" stop-color="#0284C7" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Pure Standalone Vector Symbol without Background Container -->
|
||||
<g transform="translate(14, 10)">
|
||||
<!-- Top Spark Dot (Kanji 言 Radical Spark) -->
|
||||
<circle cx="80" cy="36" r="10" fill="url(#iconCyan)" />
|
||||
|
||||
<!-- Vertical Spine Pillar (CMS Core Column) -->
|
||||
<rect x="68" y="58" width="24" height="144" rx="12" fill="url(#iconAccent)" />
|
||||
|
||||
<!-- Upper Speech Bar (Horizontal Japanese Radical Stroke) -->
|
||||
<rect x="110" y="68" width="44" height="12" rx="6" fill="url(#iconCyan)" opacity="0.95" />
|
||||
|
||||
<!-- Geometric Dynamic Chevrons forming 'K' -->
|
||||
<path
|
||||
d="M 110 120
|
||||
C 110 112, 116 105, 124 101
|
||||
L 168 76
|
||||
C 178 70, 191 78, 191 89
|
||||
C 191 95, 187 101, 182 104
|
||||
L 142 126
|
||||
C 135 130, 135 136, 142 140
|
||||
L 183 162
|
||||
C 188 165, 191 171, 191 177
|
||||
C 191 188, 178 196, 168 190
|
||||
L 124 165
|
||||
C 116 161, 110 154, 110 146
|
||||
Z"
|
||||
fill="url(#iconAccent)"
|
||||
/>
|
||||
|
||||
<!-- Central Dialogue Focal Point Node -->
|
||||
<circle cx="130" cy="133" r="9" fill="url(#iconCyan)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "kotonoba",
|
||||
"name": "Kotonoba",
|
||||
"version": "1.0.19",
|
||||
"description": "A high-performance, self-hosted, multi-tenant blog CMS built with Next.js 16, SQLite, Tailwind CSS v4, and Docker.",
|
||||
"logo": "icon.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/trixtylab/kotonoba",
|
||||
"website": "https://github.com/trixtylab/kotonoba",
|
||||
"docs": "https://github.com/TrixtyLab/kotonoba/tree/master/docs"
|
||||
},
|
||||
"tags": [
|
||||
"blog",
|
||||
"i18n",
|
||||
"nextjs",
|
||||
"web"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
jwt_secret = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"NODE_ENV=production",
|
||||
"DB_PATH=/app/data/kotonoba.db",
|
||||
"UPLOAD_DIR=/app/data/uploads",
|
||||
"JWT_SECRET=${jwt_secret}",
|
||||
"SITE_URL=https://${main_domain}",
|
||||
"ADMIN_DOMAIN=${main_domain}"
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "kotonoba"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
@@ -3,9 +3,17 @@ main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = {}
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "web"
|
||||
port = 8_069
|
||||
host = "${main_domain}"
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "config/odoo.conf"
|
||||
content = """
|
||||
[options]
|
||||
addons_path = /mnt/extra-addons
|
||||
data_dir = /var/lib/odoo
|
||||
proxy_mode = True
|
||||
"""
|
||||
|
||||
@@ -3,9 +3,17 @@ main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = {}
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "web"
|
||||
port = 8_069
|
||||
host = "${main_domain}"
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "config/odoo.conf"
|
||||
content = """
|
||||
[options]
|
||||
addons_path = /mnt/extra-addons
|
||||
data_dir = /var/lib/odoo
|
||||
proxy_mode = True
|
||||
"""
|
||||
|
||||
@@ -3,9 +3,17 @@ main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = {}
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "web"
|
||||
port = 8_069
|
||||
host = "${main_domain}"
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "config/odoo.conf"
|
||||
content = """
|
||||
[options]
|
||||
addons_path = /mnt/extra-addons
|
||||
data_dir = /var/lib/odoo
|
||||
proxy_mode = True
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
services:
|
||||
libsql:
|
||||
image: ghcr.io/tursodatabase/libsql-server:v0.24.33
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 8080
|
||||
volumes:
|
||||
- libsql-data:/var/lib/sqld
|
||||
environment:
|
||||
- SQLD_NODE=primary
|
||||
# Increase limits to handle multiple services without connection pooling
|
||||
- SQLD_MAX_CONCURRENT_CONNECTIONS=512
|
||||
- SQLD_MAX_CONCURRENT_REQUESTS=1024
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'perl -e ''use IO::Socket::INET; exit(IO::Socket::INET->new(PeerAddr=>"127.0.0.1:8080",Timeout=>1) ? 0 : 1);''',
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
# One-shot migration runner (idempotent — drizzle skips applied migrations).
|
||||
# No published image exists for it upstream, so it is built from the pinned
|
||||
# commit the ghcr.io app images were built from.
|
||||
db-migrate:
|
||||
build:
|
||||
context: https://github.com/openstatusHQ/openstatus.git#e1a98255e39c9cc9f6d4853dbce30c3f6c42514a
|
||||
dockerfile: packages/db/Dockerfile
|
||||
restart: on-failure:3
|
||||
environment:
|
||||
- DATABASE_URL=http://libsql:8080
|
||||
- DATABASE_AUTH_TOKEN=
|
||||
depends_on:
|
||||
libsql:
|
||||
condition: service_healthy
|
||||
|
||||
workflows:
|
||||
image: ghcr.io/openstatushq/openstatus-workflows:e1a9825
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 3000
|
||||
volumes:
|
||||
- workflows-data:/app/data
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- DATABASE_URL=http://libsql:8080
|
||||
- DATABASE_AUTH_TOKEN=
|
||||
- CRON_SECRET=${CRON_SECRET}
|
||||
- RESEND_API_KEY=${RESEND_API_KEY:-re_selfhost_placeholder}
|
||||
- SITE_URL=https://${DASHBOARD_HOST}
|
||||
- TINYBIRD_NOOP=true
|
||||
depends_on:
|
||||
db-migrate:
|
||||
condition: service_completed_successfully
|
||||
libsql:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/ping"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
server:
|
||||
image: ghcr.io/openstatushq/openstatus-server:e1a9825
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 3000
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- DATABASE_URL=http://libsql:8080
|
||||
- DATABASE_AUTH_TOKEN=
|
||||
- CRON_SECRET=${CRON_SECRET}
|
||||
- RESEND_API_KEY=${RESEND_API_KEY:-re_selfhost_placeholder}
|
||||
- SUPER_ADMIN_TOKEN=${SUPER_ADMIN_TOKEN}
|
||||
- SELF_HOST=true
|
||||
- FLY_REGION=self-hosted
|
||||
- TINYBIRD_NOOP=true
|
||||
- UPSTASH_REDIS_REST_URL=http://localhost:6379
|
||||
- UPSTASH_REDIS_REST_TOKEN=placeholder
|
||||
depends_on:
|
||||
db-migrate:
|
||||
condition: service_completed_successfully
|
||||
workflows:
|
||||
condition: service_healthy
|
||||
libsql:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/ping"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
dashboard:
|
||||
image: ghcr.io/openstatushq/openstatus-dashboard:e1a9825
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 3000
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- HOSTNAME=0.0.0.0
|
||||
- AUTH_TRUST_HOST=true
|
||||
- DATABASE_URL=http://libsql:8080
|
||||
- DATABASE_AUTH_TOKEN=
|
||||
- AUTH_SECRET=${AUTH_SECRET}
|
||||
- SELF_HOST=true
|
||||
- NEXT_PUBLIC_URL=https://${DASHBOARD_HOST}
|
||||
- CRON_SECRET=${CRON_SECRET}
|
||||
- RESEND_API_KEY=${RESEND_API_KEY:-re_selfhost_placeholder}
|
||||
- TINYBIRD_NOOP=true
|
||||
- UPSTASH_REDIS_REST_URL=http://localhost:6379
|
||||
- UPSTASH_REDIS_REST_TOKEN=placeholder
|
||||
depends_on:
|
||||
db-migrate:
|
||||
condition: service_completed_successfully
|
||||
workflows:
|
||||
condition: service_healthy
|
||||
libsql:
|
||||
condition: service_healthy
|
||||
server:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 45s
|
||||
|
||||
status-page:
|
||||
image: ghcr.io/openstatushq/openstatus-status-page:e1a9825
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 3000
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- HOSTNAME=0.0.0.0
|
||||
- AUTH_TRUST_HOST=true
|
||||
- DATABASE_URL=http://libsql:8080
|
||||
- DATABASE_AUTH_TOKEN=
|
||||
- AUTH_SECRET=${AUTH_SECRET}
|
||||
- SELF_HOST=true
|
||||
- NEXT_PUBLIC_URL=https://${DASHBOARD_HOST}
|
||||
- CRON_SECRET=${CRON_SECRET}
|
||||
- RESEND_API_KEY=${RESEND_API_KEY:-re_selfhost_placeholder}
|
||||
- TINYBIRD_NOOP=true
|
||||
depends_on:
|
||||
db-migrate:
|
||||
condition: service_completed_successfully
|
||||
workflows:
|
||||
condition: service_healthy
|
||||
libsql:
|
||||
condition: service_healthy
|
||||
server:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 45s
|
||||
|
||||
# Ingest server for private-location probes (the probe itself,
|
||||
# ghcr.io/openstatushq/private-location, runs wherever you monitor from).
|
||||
private-location:
|
||||
image: ghcr.io/openstatushq/openstatus-private-location:e1a9825
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
- PORT=8080
|
||||
- GIN_MODE=release
|
||||
- DB_URL=http://libsql:8080
|
||||
- WORKFLOWS_URL=http://workflows:3000
|
||||
- CRON_SECRET=${CRON_SECRET}
|
||||
- TINYBIRD_URL=${TINYBIRD_URL}
|
||||
- TINYBIRD_TOKEN=${TINYBIRD_TOKEN}
|
||||
depends_on:
|
||||
server:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/health"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
libsql-data:
|
||||
workflows-data:
|
||||
@@ -0,0 +1,60 @@
|
||||
# OpenStatus
|
||||
|
||||
OpenStatus is deployed here in self-hosted mode following the official Docker
|
||||
setup (`DOCKER.md` / `docker-compose.github-packages.yaml` upstream), with all
|
||||
app images pinned to the same upstream commit (`e1a9825`).
|
||||
|
||||
## Services
|
||||
|
||||
| Service | Purpose |
|
||||
|---|---|
|
||||
| dashboard | Admin UI (your first domain) |
|
||||
| status-page | Public status pages (your second domain, pages served at `/<slug>`) |
|
||||
| server | API backend (tRPC / public API) |
|
||||
| workflows | Background jobs / alerting |
|
||||
| libsql | Local libSQL (Turso) database — no cloud account needed |
|
||||
| db-migrate | One-shot migration runner (built from the pinned upstream commit, exits after applying) |
|
||||
| private-location | Ingest server that receives check results from your monitoring probes |
|
||||
|
||||
Database migrations run automatically on every deploy (the one-shot
|
||||
`db-migrate` service is idempotent).
|
||||
|
||||
Note: `db-migrate` is built from source (upstream publishes no image for it),
|
||||
so the first deployment takes a few extra minutes while it builds.
|
||||
|
||||
## First login
|
||||
|
||||
1. Open the dashboard domain and choose **magic link** login with your e-mail.
|
||||
2. If you did not configure `RESEND_API_KEY`, the magic link is not e-mailed —
|
||||
it is printed to the **dashboard service logs**. Open the dashboard
|
||||
container logs in Dokploy and copy the `>>> Magic Link: ...` URL into your
|
||||
browser. If the printed link starts with `http://0.0.0.0:3000`, replace that
|
||||
host with your dashboard domain before opening it. (Optionally, once HTTPS
|
||||
is enabled on your dashboard domain, you can add
|
||||
`AUTH_URL=https://<your-dashboard-domain>` to the dashboard service in the
|
||||
compose file so links are generated with the right host — do not set it to
|
||||
an empty value or a plain-HTTP domain, as that breaks the auth flow.)
|
||||
3. Create a workspace and a status page with a slug; it becomes available at
|
||||
`https://<status-page-domain>/<slug>`.
|
||||
|
||||
## Running checks (probes)
|
||||
|
||||
Self-hosted OpenStatus runs monitors through **private location probes**
|
||||
(`ghcr.io/openstatushq/private-location`) that you deploy wherever you want to
|
||||
monitor from. Point the probe at this stack's ingest server:
|
||||
|
||||
- Expose the `private-location` service (port 8080) on a domain in Dokploy, and
|
||||
- set `OPENSTATUS_INGEST_URL` on the probe to that URL, together with the
|
||||
`OPENSTATUS_KEY` you create in the dashboard.
|
||||
|
||||
See the upstream guide: https://www.openstatus.dev/docs (Guides → How to
|
||||
Self-Host openstatus).
|
||||
|
||||
## Analytics (optional)
|
||||
|
||||
Tinybird analytics are disabled by default (`TINYBIRD_NOOP=true`), so charts
|
||||
show no data but everything else works. To enable analytics, run a Tinybird
|
||||
Local container or use Tinybird Cloud, deploy the `packages/tinybird` project
|
||||
with the `tb` CLI, and set `TINYBIRD_URL` / `TINYBIRD_TOKEN` in the
|
||||
environment (plus `TINY_BIRD_API_KEY` and remove `TINYBIRD_NOOP`) — see the
|
||||
upstream self-hosting guide.
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "openstatus",
|
||||
"name": "OpenStatus",
|
||||
"version": "e1a9825",
|
||||
"description": "OpenStatus is an open-source synthetic monitoring platform with beautiful status pages, uptime and API monitoring as code, and alerting.",
|
||||
"logo": "openstatus.png",
|
||||
"links": {
|
||||
"github": "https://github.com/openstatusHQ/openstatus",
|
||||
"website": "https://www.openstatus.dev",
|
||||
"docs": "https://www.openstatus.dev/docs"
|
||||
},
|
||||
"tags": [
|
||||
"monitoring",
|
||||
"status-page",
|
||||
"uptime"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,34 @@
|
||||
[variables]
|
||||
dashboard_domain = "${domain}"
|
||||
status_page_domain = "${domain}"
|
||||
auth_secret = "${base64:32}"
|
||||
cron_secret = "${password:32}"
|
||||
super_admin_token = "${password:32}"
|
||||
|
||||
[config]
|
||||
|
||||
# Dashboard (admin UI — log in here)
|
||||
[[config.domains]]
|
||||
serviceName = "dashboard"
|
||||
port = 3000
|
||||
host = "${dashboard_domain}"
|
||||
|
||||
# Public status pages (served at /<page-slug>)
|
||||
[[config.domains]]
|
||||
serviceName = "status-page"
|
||||
port = 3000
|
||||
host = "${status_page_domain}"
|
||||
|
||||
[config.env]
|
||||
DASHBOARD_HOST = "${dashboard_domain}"
|
||||
STATUS_PAGE_HOST = "${status_page_domain}"
|
||||
AUTH_SECRET = "${auth_secret}"
|
||||
CRON_SECRET = "${cron_secret}"
|
||||
SUPER_ADMIN_TOKEN = "${super_admin_token}"
|
||||
# Optional: Resend API key for outgoing e-mails. Without it, magic-link
|
||||
# login URLs are printed to the dashboard container logs instead.
|
||||
RESEND_API_KEY = ""
|
||||
# Optional: Tinybird analytics for the private-location ingest server
|
||||
# (see instructions — analytics are disabled/no-op by default)
|
||||
TINYBIRD_URL = ""
|
||||
TINYBIRD_TOKEN = ""
|
||||
@@ -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'
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 96 KiB |
@@ -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}"
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
plausible_events_db:
|
||||
image: clickhouse/clickhouse-server:24.3.3.102-alpine
|
||||
image: clickhouse/clickhouse-server:24.12-alpine
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
@@ -21,20 +21,31 @@ services:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
environment:
|
||||
# Required since ClickHouse 24.11+ images: skip the default-user setup so
|
||||
# Plausible can connect as "default" without a password (matches the
|
||||
# official CE v3 compose.yml).
|
||||
- CLICKHOUSE_SKIP_USER_SETUP=1
|
||||
|
||||
plausible:
|
||||
image: ghcr.io/plausible/community-edition:v2.1.5
|
||||
image: ghcr.io/plausible/community-edition:v3.2.1
|
||||
restart: always
|
||||
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
|
||||
depends_on:
|
||||
- plausible_db
|
||||
- plausible_events_db
|
||||
volumes:
|
||||
- plausible-data:/var/lib/plausible
|
||||
environment:
|
||||
- TMPDIR=/var/lib/plausible/tmp
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
driver: local
|
||||
plausible-data:
|
||||
driver: local
|
||||
event-data:
|
||||
driver: local
|
||||
event-logs:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "plausible",
|
||||
"name": "Plausible",
|
||||
"version": "v2.1.5",
|
||||
"version": "v3.2.1",
|
||||
"description": "Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
pocket-id:
|
||||
image: ghcr.io/pocket-id/pocket-id:v1
|
||||
image: pocketid/pocket-id:v2.14.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- APP_URL
|
||||
- TRUST_PROXY
|
||||
- ENCRYPTION_KEY
|
||||
APP_URL: ${APP_URL}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
TRUST_PROXY: ${TRUST_PROXY}
|
||||
volumes:
|
||||
- pocket-id-data:/app/data
|
||||
expose:
|
||||
- "1411"
|
||||
healthcheck:
|
||||
test: [ "CMD", "/app/pocket-id", "healthcheck" ]
|
||||
test: ["CMD", "/app/pocket-id", "healthcheck"]
|
||||
interval: 1m30s
|
||||
timeout: 5s
|
||||
retries: 2
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
{
|
||||
"id": "pocket-id",
|
||||
"name": "Pocket ID",
|
||||
"version": "v1",
|
||||
"description": "A simple and easy-to-use OIDC provider that allows users to authenticate with their passkeys to your services.",
|
||||
"version": "2.14.0",
|
||||
"description": "Pocket ID is a simple OpenID Connect and OAuth 2.0 provider that lets users authenticate to applications with passkeys.",
|
||||
"logo": "pocket-id.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/pocket-id/pocket-id",
|
||||
"website": "https://pocket-id.org/",
|
||||
"docs": "https://pocket-id.org/docs"
|
||||
"docs": "https://pocket-id.org/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"authentication",
|
||||
"identity",
|
||||
"auth"
|
||||
"sso",
|
||||
"oidc",
|
||||
"oauth2",
|
||||
"passkeys",
|
||||
"security",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
encryption_key = "${base64:32}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
@@ -10,6 +11,6 @@ port = 1411
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
ENCRYPTION_KEY = "CHANGEME: openssl rand -base64 32"
|
||||
APP_URL = "http://${main_domain}"
|
||||
APP_URL = "https://${main_domain}"
|
||||
ENCRYPTION_KEY = "${encryption_key}"
|
||||
TRUST_PROXY = "true"
|
||||
|
||||
@@ -12,12 +12,15 @@ services:
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
DATABASE_URL: "postgresql://${DB_USER}:${DB_PASSWORD}@postiz-postgres:5432/${DB_NAME}"
|
||||
REDIS_URL: "redis://postiz-redis:6379"
|
||||
BACKEND_INTERNAL_URL: "http://localhost:3000"
|
||||
# "localhost" resolves to ::1 first, but the backend only listens on IPv4.
|
||||
BACKEND_INTERNAL_URL: "http://127.0.0.1:3000"
|
||||
TEMPORAL_ADDRESS: "temporal:7233"
|
||||
IS_GENERAL: "true"
|
||||
DISABLE_REGISTRATION: "false"
|
||||
DISABLE_REGISTRATION: ${DISABLE_REGISTRATION:-false}
|
||||
RUN_CRON: "true"
|
||||
NOT_SECURED: "true"
|
||||
# Postiz only checks whether NOT_SECURED is set (any value, even "false",
|
||||
# enables insecure mode). The template is served over HTTPS, so keep it
|
||||
# unset to get secure cookies.
|
||||
STORAGE_PROVIDER: "local"
|
||||
UPLOAD_DIRECTORY: "/uploads"
|
||||
NEXT_PUBLIC_UPLOAD_DIRECTORY: "/uploads"
|
||||
@@ -25,12 +28,22 @@ services:
|
||||
# === Social Media API Settings ===
|
||||
# These variables should be defined in the Dokploy Environment tab.
|
||||
# For more configuration options and details, please refer to the official documentation.
|
||||
# Every provider Postiz reads must be listed here, otherwise setting it in
|
||||
# the Environment tab has no effect: it never reaches the container.
|
||||
FACEBOOK_APP_ID: ${FACEBOOK_APP_ID}
|
||||
FACEBOOK_APP_SECRET: ${FACEBOOK_APP_SECRET}
|
||||
INSTAGRAM_APP_ID: ${INSTAGRAM_APP_ID}
|
||||
INSTAGRAM_APP_SECRET: ${INSTAGRAM_APP_SECRET}
|
||||
LINKEDIN_CLIENT_ID: ${LINKEDIN_CLIENT_ID}
|
||||
LINKEDIN_CLIENT_SECRET: ${LINKEDIN_CLIENT_SECRET}
|
||||
X_API_KEY: ${X_API_KEY}
|
||||
X_API_SECRET: ${X_API_SECRET}
|
||||
# YOUTUBE_CLIENT_ID/SECRET also power "Sign in with Google" (Postiz's
|
||||
# Google auth provider reads YOUTUBE_CLIENT_ID).
|
||||
YOUTUBE_CLIENT_ID: ${YOUTUBE_CLIENT_ID}
|
||||
YOUTUBE_CLIENT_SECRET: ${YOUTUBE_CLIENT_SECRET}
|
||||
TIKTOK_CLIENT_ID: ${TIKTOK_CLIENT_ID}
|
||||
TIKTOK_CLIENT_SECRET: ${TIKTOK_CLIENT_SECRET}
|
||||
|
||||
volumes:
|
||||
- postiz-config:/config/
|
||||
@@ -41,7 +54,8 @@ services:
|
||||
postiz-redis:
|
||||
condition: service_healthy
|
||||
temporal:
|
||||
condition: service_started
|
||||
# Postiz crashes on boot if Temporal is not accepting connections yet.
|
||||
condition: service_healthy
|
||||
|
||||
postiz-postgres:
|
||||
image: postgres:17-alpine
|
||||
@@ -74,6 +88,7 @@ services:
|
||||
|
||||
temporal-elasticsearch:
|
||||
image: elasticsearch:7.17.27
|
||||
restart: always
|
||||
environment:
|
||||
- cluster.routing.allocation.disk.threshold_enabled=true
|
||||
- cluster.routing.allocation.disk.watermark.low=512mb
|
||||
@@ -87,6 +102,7 @@ services:
|
||||
|
||||
temporal-postgresql:
|
||||
image: postgres:16
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${TEMPORAL_PASSWORD}
|
||||
POSTGRES_USER: ${TEMPORAL_USER}
|
||||
@@ -100,6 +116,7 @@ services:
|
||||
|
||||
temporal:
|
||||
image: temporalio/auto-setup:1.28.1
|
||||
restart: always
|
||||
depends_on:
|
||||
temporal-postgresql:
|
||||
condition: service_healthy
|
||||
@@ -116,7 +133,8 @@ services:
|
||||
- ES_VERSION=v7
|
||||
- TEMPORAL_NAMESPACE=default
|
||||
healthcheck:
|
||||
test: ["CMD", "tctl", "--address", "127.0.0.1:7233", "workflow", "list"]
|
||||
# tctl needs an explicit namespace, and Temporal does not bind 127.0.0.1.
|
||||
test: ["CMD-SHELL", "tctl --address $$(hostname -i):7233 --namespace default workflow list > /dev/null 2>&1"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
@@ -18,10 +18,16 @@ env = [
|
||||
"TEMPORAL_PASSWORD=${temporal_password}",
|
||||
"FACEBOOK_APP_ID=",
|
||||
"FACEBOOK_APP_SECRET=",
|
||||
"INSTAGRAM_APP_ID=",
|
||||
"INSTAGRAM_APP_SECRET=",
|
||||
"LINKEDIN_CLIENT_ID=",
|
||||
"LINKEDIN_CLIENT_SECRET=",
|
||||
"X_API_KEY=",
|
||||
"X_API_SECRET="
|
||||
"X_API_SECRET=",
|
||||
"YOUTUBE_CLIENT_ID=",
|
||||
"YOUTUBE_CLIENT_SECRET=",
|
||||
"TIKTOK_CLIENT_ID=",
|
||||
"TIKTOK_CLIENT_SECRET="
|
||||
]
|
||||
mounts = []
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
quireink:
|
||||
image: quireink/quireink:2.2.1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
SITE_URL: https://${QUIREINK_HOST}
|
||||
volumes:
|
||||
- quireink-data:/var/lib/quire/data
|
||||
- quireink-uploads:/var/lib/quire/uploads
|
||||
|
||||
volumes:
|
||||
quireink-data: {}
|
||||
quireink-uploads: {}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "quireink",
|
||||
"name": "Quire Ink",
|
||||
"version": "2.2.1",
|
||||
"description": "A self-hosted blog in one container: a real editor over Markdown with tables, footnotes and mathematics, scheduled publishing, newsletters over your own SMTP, comments with a built-in spam gate, cookie-free analytics, full-text search and a book reading mode. Importers bring a WordPress, Ghost, Substack or Medium blog across with its redirects and images. There is no database service to run and nothing is fetched from a third party: two SQLite files and an uploads folder are the whole of it. After the first start, open the container logs for the one-time link that claims the blog and creates your account.",
|
||||
"logo": "quireink.png",
|
||||
"links": {
|
||||
"github": "https://github.com/joiha-steven/quireink",
|
||||
"website": "https://quireink.com",
|
||||
"docs": "https://github.com/joiha-steven/quireink/blob/main/docs/self-host-docker.md"
|
||||
},
|
||||
"tags": ["cms", "self-hosted", "blog"]
|
||||
}
|
||||
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,11 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = ["QUIREINK_HOST=${main_domain}"]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "quireink"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,33 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
rustrak-server:
|
||||
image: rustrak/rustrak-server:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- PORT=8080
|
||||
- RUST_LOG=info
|
||||
- SSL_PROXY=true
|
||||
- SESSION_SECRET_KEY=${SESSION_SECRET_KEY}
|
||||
- CREATE_SUPERUSER=${CREATE_SUPERUSER}
|
||||
volumes:
|
||||
- rustrak_data:/data
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
rustrak-ui:
|
||||
image: rustrak/rustrak-ui:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- RUSTRAK_API_URL=${RUSTRAK_API_URL}
|
||||
# Next.js standalone binds to $HOSTNAME (the container id) by default, which is
|
||||
# unreachable from Traefik on the Dokploy network -> 502. Bind to all interfaces.
|
||||
- HOSTNAME=0.0.0.0
|
||||
expose:
|
||||
- 3000
|
||||
depends_on:
|
||||
- rustrak-server
|
||||
|
||||
volumes:
|
||||
rustrak_data:
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"id": "rustrak-sqlite",
|
||||
"name": "Rustrak (Full Stack SQLite)",
|
||||
"version": "latest",
|
||||
"description": "Self-hosted error tracking compatible with Sentry SDKs. Full stack with the Rust server, web dashboard and built-in SQLite storage. No external database required.",
|
||||
"logo": "rustrak.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/rustrak/rustrak",
|
||||
"website": "https://rustrak.github.io/rustrak/",
|
||||
"docs": "https://rustrak.github.io/rustrak/getting-started/overview"
|
||||
},
|
||||
"tags": [
|
||||
"monitoring",
|
||||
"error-tracking",
|
||||
"sentry",
|
||||
"rust",
|
||||
"observability",
|
||||
"sqlite"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,25 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
api_domain = "${domain}"
|
||||
session_secret_key = "${password:64}"
|
||||
admin_email = "${email}"
|
||||
admin_password = "${password:16}"
|
||||
create_superuser = "${admin_email}:${admin_password}"
|
||||
rustrak_api_url = "https://${api_domain}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"SESSION_SECRET_KEY=${session_secret_key}",
|
||||
"CREATE_SUPERUSER=${create_superuser}",
|
||||
"RUSTRAK_API_URL=${rustrak_api_url}",
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "rustrak-ui"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "rustrak-server"
|
||||
port = 8080
|
||||
host = "${api_domain}"
|
||||
@@ -0,0 +1,16 @@
|
||||
services:
|
||||
silo:
|
||||
image: pgsty/silo:RELEASE.2026-08-06T00-00-00Z
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 9000
|
||||
- 9001
|
||||
environment:
|
||||
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- silo:/data
|
||||
command: server /data --console-address ":9001"
|
||||
|
||||
volumes:
|
||||
silo:
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="570" height="570" viewBox="230 213 570 570"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
role="img" aria-labelledby="title desc"
|
||||
shape-rendering="geometricPrecision">
|
||||
<title id="title">SILO emblem</title>
|
||||
<desc id="desc">A smooth circular SILO mark with a peaked roof, flowing left wall, columns, and a curved base.</desc>
|
||||
|
||||
<defs>
|
||||
<linearGradient id="silo-color" x1="276" y1="720" x2="742" y2="286" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#064A83"/>
|
||||
<stop offset="0.5" stop-color="#007FA8"/>
|
||||
<stop offset="1" stop-color="#22C7C9"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<g fill="url(#silo-color)">
|
||||
<!-- Outer circular band, intentionally opened at the lower-right plinth. -->
|
||||
<path d="
|
||||
M 734 676
|
||||
A 283.5 278.5 0 1 0 310 692
|
||||
L 359 692
|
||||
A 247 248.5 0 1 1 688 676
|
||||
Z"/>
|
||||
|
||||
<!-- Flowing left wall; its upper tangent matches the inner ellipse. -->
|
||||
<path d="
|
||||
M 300.57 375
|
||||
C 292 390 300 430 328 450
|
||||
C 343 461 357 472 374 481
|
||||
C 410 501 426 517 426 544
|
||||
L 426 676
|
||||
L 336 676
|
||||
C 308 647 286 608 274 565
|
||||
C 262 522 263 479 272 439
|
||||
C 278 413 286 389 300.57 375
|
||||
Z"/>
|
||||
|
||||
<!-- Right column with tangent-continuous upper shoulder. -->
|
||||
<path d="
|
||||
M 602 373
|
||||
Q 602 368 607 370
|
||||
C 619 374 634 381 634 389
|
||||
L 634 647
|
||||
Q 634 649 636 649
|
||||
L 708 649
|
||||
L 734 676
|
||||
L 602 676
|
||||
Z"/>
|
||||
|
||||
<!-- Lower circular cap. -->
|
||||
<path d="
|
||||
M 310 692
|
||||
L 714 692
|
||||
C 668 743 596 774 512 774
|
||||
C 428 774 355 743 310 692
|
||||
Z"/>
|
||||
|
||||
<!-- Main silo body, with a tangent-continuous right shoulder. -->
|
||||
<path d="
|
||||
M 389 389
|
||||
C 389 374 447 351 512 351
|
||||
C 540 351 565 354 580 359
|
||||
Q 583 360 583 364
|
||||
L 583 676
|
||||
L 443 676
|
||||
L 443 541
|
||||
C 443 509 426 490 389 470
|
||||
Z"/>
|
||||
|
||||
<!-- Peaked roof with softly tapered, burr-free tips. -->
|
||||
<path d="
|
||||
M 512 274
|
||||
L 647 365
|
||||
Q 649 370 647 376
|
||||
C 609 350 563 337 512 337
|
||||
C 460 337 414 350 377 376
|
||||
Q 375 370 377 365
|
||||
Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "silo",
|
||||
"name": "Silo",
|
||||
"version": "RELEASE.2026-08-06T00-00-00Z",
|
||||
"description": "S3-Compatible Object Storage. A MinIO fork maintained by PGSTY",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/pgsty/silo",
|
||||
"website": "https://silo.pgsty.com",
|
||||
"docs": "https://silo.pgsty.com/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"s3",
|
||||
"file",
|
||||
"storage"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
console_domain = "${domain}"
|
||||
password = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "silo"
|
||||
port = 9000
|
||||
host = "${main_domain}"
|
||||
[[config.domains]]
|
||||
serviceName = "silo"
|
||||
port = 9001
|
||||
host = "${console_domain}"
|
||||
|
||||
[config.env]
|
||||
MINIO_ROOT_USER = "silo_usr"
|
||||
MINIO_ROOT_PASSWORD = "${password}"
|
||||
@@ -6,7 +6,7 @@
|
||||
"logo": "image.png",
|
||||
"links": {
|
||||
"github": "https://github.com/flomp/wanderer",
|
||||
"website": "https://wanderer.app",
|
||||
"website": "https://wanderer.to",
|
||||
"docs": "https://github.com/flomp/wanderer#readme"
|
||||
},
|
||||
"tags": [
|
||||
|
||||
@@ -2,13 +2,17 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
wupnext:
|
||||
image: docker.io/javierlnmn/wupnext:latest
|
||||
image: docker.io/javierlnmn/wupnext:0.3.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
DEBUG: "False"
|
||||
ALLOWED_HOSTS: ${ALLOWED_HOSTS}
|
||||
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS}
|
||||
# Public base URL, used for links in emails
|
||||
SITE_URL: ${SITE_URL}
|
||||
# Public origin the MCP server advertises to clients during discovery
|
||||
MCP_BASE_URL: ${MCP_BASE_URL}
|
||||
EMAIL_BACKEND: anymail.backends.resend.EmailBackend
|
||||
DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL}
|
||||
RESEND_API_KEY: ${RESEND_API_KEY}
|
||||
@@ -31,9 +35,14 @@ services:
|
||||
DEBUG: "False"
|
||||
ALLOWED_HOSTS: ${ALLOWED_HOSTS}
|
||||
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS}
|
||||
SITE_URL: ${SITE_URL}
|
||||
EMAIL_BACKEND: anymail.backends.resend.EmailBackend
|
||||
DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL}
|
||||
RESEND_API_KEY: ${RESEND_API_KEY}
|
||||
# Django Q2 cluster (ORM broker, no Redis)
|
||||
Q_WORKERS: ${Q_WORKERS}
|
||||
Q_TIMEOUT: ${Q_TIMEOUT}
|
||||
Q_RETRY: ${Q_RETRY}
|
||||
volumes:
|
||||
- wupnext-db:/app/db
|
||||
depends_on:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "wupnext",
|
||||
"name": "WupNext",
|
||||
"version": "latest",
|
||||
"description": "WupNext is a self-hosted to-do and task queue app: groups, subtasks, deadlines, a built-in pomodoro timer, and daily email reminders. Server-rendered Django with HTMX, backed by SQLite.",
|
||||
"description": "WupNext is a self-hosted to-do and task queue app: groups, subtasks, deadlines, a built-in pomodoro timer, email reminders, and an MCP server so an AI client can work the board. Server-rendered Django with HTMX, backed by SQLite.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/javierlnmn/wupnext",
|
||||
@@ -14,6 +14,7 @@
|
||||
"tasks",
|
||||
"productivity",
|
||||
"pomodoro",
|
||||
"mcp",
|
||||
"django",
|
||||
"self-hosted"
|
||||
]
|
||||
|
||||
@@ -12,11 +12,16 @@ env = [
|
||||
"SECRET_KEY=${secret_key}",
|
||||
"ALLOWED_HOSTS=${main_domain}",
|
||||
"CSRF_TRUSTED_ORIGINS=https://${main_domain}",
|
||||
"SITE_URL=https://${main_domain}",
|
||||
"MCP_BASE_URL=https://${main_domain}",
|
||||
"DEFAULT_FROM_EMAIL=${from_email}",
|
||||
"RESEND_API_KEY=${resend_api_key}",
|
||||
"DJANGO_SUPERUSER_USERNAME=${admin_username}",
|
||||
"DJANGO_SUPERUSER_EMAIL=${admin_email}",
|
||||
"DJANGO_SUPERUSER_PASSWORD=${admin_password}"
|
||||
"DJANGO_SUPERUSER_PASSWORD=${admin_password}",
|
||||
"Q_WORKERS=2",
|
||||
"Q_TIMEOUT=60",
|
||||
"Q_RETRY=120"
|
||||
]
|
||||
mounts = []
|
||||
|
||||
|
||||