Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d0ad0a6fc | ||
|
|
820cd31362 |
@@ -6,13 +6,12 @@ New PR of [Template Name]
|
||||
|
||||
Before submitting this PR, please make sure that:
|
||||
|
||||
- [ ] I have read the suggestions in the README.md file https://github.com/Dokploy/templates?tab=readme-ov-file#general-requirements-when-creating-a-template
|
||||
- [ ] I have read the suggestions in the README.md file https://github.com/Dokploy/templates?tab=readme-ov-file#general-suggestions-when-creating-a-template
|
||||
- [ ] I have tested the template in my instance, so the maintainers don't spend time trying to figure out what's wrong.
|
||||
- [ ] I have added tests that demonstrate that my correction works or that my new feature works.
|
||||
|
||||
## Issues related (if applicable)
|
||||
|
||||
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`
|
||||
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`, `fixes #ISSUE_NUMBER`, `resolves #ISSUE_NUMBER`
|
||||
|
||||
|
||||
## Screenshots or Videos
|
||||
Example: `closes #123`
|
||||
@@ -8,6 +8,7 @@ Key components:
|
||||
|
||||
- **Blueprints**: Self-contained templates with `docker-compose.yml` (service definitions) and `template.toml` (Dokploy-specific configuration for domains, env vars, mounts).
|
||||
- **meta.json**: Centralized index of all templates, aggregated from blueprint metadata. Entries include `id`, `name`, `version`, `description`, `logo`, `links`, and `tags`.
|
||||
- **app/**: Vite-based React frontend for local preview/development (runs at http://localhost:5173). Copies blueprints and meta.json to dist during build.
|
||||
- **Scripts**: Node.js tools in root and `build-scripts/` for maintaining `meta.json` (deduplication, sorting, validation).
|
||||
|
||||
Data flow: New templates added to `blueprints/` → Metadata updated in `meta.json` → Processing scripts ensure consistency → App builds include static blueprints/meta for preview.
|
||||
@@ -21,6 +22,7 @@ The "why": Enables rapid, standardized deployment of 200+ OSS apps on Dokploy wi
|
||||
- `docker-compose.yml`: Standard Docker Compose v3.8. Avoid `ports`, `container_name`, `networks`—Dokploy handles isolation via internal networks.
|
||||
- `template.toml`: Defines variables (e.g., `${domain}`), domains (service:port → host), env vars, and mounts. Use helpers like `${password:32}`, `${uuid}`, `${jwt:secret_var}`.
|
||||
- `logo.svg/png`: Service icon, referenced in `meta.json`.
|
||||
- `app/vite.config.ts`: Configures build to copy `blueprints/*` and `meta.json` to dist root for static serving.
|
||||
- `dedupe-and-sort-meta.js`: Standalone script—reads `meta.json`, removes duplicate `id`s (keeps first), sorts by `id` (case-insensitive), creates timestamped backup.
|
||||
- `build-scripts/process-meta.js`: Advanced processor with CLI options (`--verbose`, `--no-backup`, `--input`/`--output`), JSON schema validation (required: `id`, `name`, `version`, `description`, `links.github`, `logo`, `tags` array).
|
||||
|
||||
@@ -30,8 +32,6 @@ Exemplary blueprint: `blueprints/ghost/`—`docker-compose.yml` exposes port 236
|
||||
|
||||
1. **Add/Update Template**:
|
||||
|
||||
- **REQUIREMENT**: Service **MUST** be open source. Only add templates for applications with an open-source license (e.g., MIT, Apache, GPL, AGPL). Proprietary or closed-source services are not allowed.
|
||||
- **Verify Docker Images**: Before using any Docker image in `docker-compose.yml`, verify it exists using `docker manifest inspect <image:tag>` (e.g., `docker manifest inspect docker.io/bitnami/discourse:3.5.0`). This ensures the image is available and prevents deployment failures.
|
||||
- Create `blueprints/<id>/` (e.g., `ghost`).
|
||||
- Implement `docker-compose.yml` (single service typical; use volumes for persistence).
|
||||
- Configure `template.toml`—reference vars in `[config.domains]`, `[config.env]`, `[config.mounts]`.
|
||||
@@ -39,7 +39,14 @@ Exemplary blueprint: `blueprints/ghost/`—`docker-compose.yml` exposes port 236
|
||||
- Run `node dedupe-and-sort-meta.js --backup` to validate/sort.
|
||||
- Commit; PR triggers Dokploy preview (base64 import for testing).
|
||||
|
||||
2. **CI/CD**:
|
||||
2. **Local Development**:
|
||||
|
||||
- App: `cd app && pnpm install && pnpm dev` (Vite dev server).
|
||||
- Meta processing: `npm run process-meta` or `make process-meta` (uses Makefile targets: `validate`, `check`, `build`).
|
||||
- Build app: `cd app && pnpm build`—copies blueprints/meta to `dist/` for static hosting.
|
||||
- Test template: Use PR preview URL or local Dokploy instance; import base64 from template card.
|
||||
|
||||
3. **CI/CD**:
|
||||
- `.github/workflows/validate-meta.yml` (if present): Runs validation on push/PR—fails on duplicates, invalid JSON, missing fields.
|
||||
- Integrate processing: Add `npm run process-meta` to build steps; use `--no-backup` in CI.
|
||||
|
||||
@@ -47,22 +54,20 @@ No tests in repo—focus on manual validation via scripts and Dokploy deploys. D
|
||||
|
||||
## Conventions and Patterns
|
||||
|
||||
- **Open Source Requirement**: **ALL services MUST be open source**. Only applications with open-source licenses (MIT, Apache, GPL, AGPL, etc.) are allowed. Proprietary or closed-source services are strictly prohibited.
|
||||
- **Template IDs**: Lowercase, kebab-case (e.g., `active-pieces`); unique across repo—enforced by dedupe script.
|
||||
- **Docker Compose**: Minimal—omit `ports` (Dokploy proxies), persistent volumes (e.g., `- db-data:/var/lib/postgresql/data`). Services named after folder (e.g., `ghost` service).
|
||||
- **Docker Compose**: Minimal—omit `ports` (Dokploy proxies), `restart: unless-stopped`, persistent volumes (e.g., `- db-data:/var/lib/postgresql/data`). Services named after folder (e.g., `ghost` service).
|
||||
- **template.toml**:
|
||||
- Variables: `[variables] main_domain = "${domain}"`; use helpers for secrets (`${password:64}`, `${base64:32}`).
|
||||
- Domains: `[[config.domains]] serviceName = "<service>" port = 80 host = "${main_domain}"` (path="/" optional).
|
||||
- Env: `[[config.env]]` array of "KEY=VALUE" strings, interpolating vars (e.g., "DB_PASSWORD=${db_pass}").
|
||||
- **URL Variables**: When environment variables require URLs (e.g., `WEB_URL`, `NEXTAUTH_URL`, `PUBLIC_URL`), **always use HTTP by default** (e.g., `"http://${main_domain}"`). HTTPS should only be used if explicitly required by the application or when using a reverse proxy with SSL termination.
|
||||
- Mounts: `[[config.mounts]] filePath = "/etc/config" content = """multi-line\ncontent"""`.
|
||||
- JWT helper: `${jwt:secret_var:payload_var}` for auth tokens; payload as JSON string with `exp: ${timestamps:YYYY-MM-DDTHH:mm:ssZ}`.
|
||||
- **Meta.json**: Entries as JSON objects; tags array of lowercase strings (e.g., ["monitoring", "database"]); links object with `github`, `website`, `docs`.
|
||||
- **No Networks**: Rely on Dokploy's isolated deployments—avoid explicit `networks:`.
|
||||
- **Versions**: Pin images to specific versions in `docker-compose.yml` (e.g., `ghost:5.82.0-alpine`); match in `meta.json.version`. **NEVER use `latest` tag**—it can break templates when upstream images change unexpectedly. **Always verify image exists** using `docker manifest inspect <image:tag>` before committing.
|
||||
- **Versions**: Pin images to specific versions in `docker-compose.yml` (e.g., `ghost:5.82.0-alpine`); match in `meta.json.version`.
|
||||
- **Logos**: SVG preferred; size ~128x128; file name in `meta.json.logo` (e.g., "ghost.svg").
|
||||
|
||||
Cross-component: Templates are independent and ship as static blueprints/meta.
|
||||
Cross-component: No runtime communication—templates independent. App consumes static blueprints/meta for UI rendering (e.g., search, cards via React components in `app/src/`).
|
||||
|
||||
## Integration Points
|
||||
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
name: Audit All Blueprints
|
||||
|
||||
# The per-PR validators only look at blueprints a PR touches, which is the right
|
||||
# call for CI cost but means a template is checked when it lands and then never
|
||||
# again. When a rule is added or tightened, every existing blueprint that breaks
|
||||
# it stays broken with nothing surfacing it (see #1047).
|
||||
#
|
||||
# This job runs the same validators over ALL blueprints on a schedule, so drift
|
||||
# is reported instead of accumulating. It never runs on pull_request, so it
|
||||
# cannot block a contributor for a pre-existing problem they did not introduce.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Mondays, 04:00 UTC
|
||||
- cron: "0 4 * * 1"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
fail_on_findings:
|
||||
description: "Exit non-zero when findings exist (default: report only)"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.16.0
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd build-scripts && pnpm install
|
||||
|
||||
- name: Validate per-template metadata
|
||||
run: node build-scripts/generate-meta.js --check
|
||||
|
||||
- name: Audit every blueprint
|
||||
id: audit
|
||||
run: |
|
||||
# $RUNNER_TEMP is per-job; also cleared explicitly so a re-run or a
|
||||
# partially-executed earlier step can never leak into the summary.
|
||||
WORK="${RUNNER_TEMP:-/tmp}"
|
||||
OUT="$WORK/validator-out.txt"
|
||||
REPORT="$WORK/audit-report.md"
|
||||
SUMMARY="$WORK/audit-summary.md"
|
||||
rm -f "$OUT" "$REPORT" "$SUMMARY"
|
||||
|
||||
COMPOSE_FAILED=""
|
||||
TOML_FAILED=""
|
||||
|
||||
for dir in blueprints/*/; do
|
||||
id="$(basename "$dir")"
|
||||
|
||||
# Run unconditionally: a blueprint with no docker-compose.yml or no
|
||||
# template.toml is itself a finding, and the validators already
|
||||
# report a missing file clearly. Skipping them would hide exactly
|
||||
# the blueprints most likely to be broken.
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-docker-compose.ts \
|
||||
--file "../$dir/docker-compose.yml") > "$OUT" 2>&1; then
|
||||
COMPOSE_FAILED="$COMPOSE_FAILED $id"
|
||||
{
|
||||
echo "### \`$id\` — docker-compose.yml"
|
||||
echo '```'
|
||||
grep '❌' "$OUT" || cat "$OUT"
|
||||
echo '```'
|
||||
} >> "$REPORT"
|
||||
fi
|
||||
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-template.ts \
|
||||
--dir "../$dir") > "$OUT" 2>&1; then
|
||||
TOML_FAILED="$TOML_FAILED $id"
|
||||
{
|
||||
echo "### \`$id\` — template.toml"
|
||||
echo '```'
|
||||
grep '❌' "$OUT" || cat "$OUT"
|
||||
echo '```'
|
||||
} >> "$REPORT"
|
||||
fi
|
||||
done
|
||||
|
||||
COMPOSE_COUNT=$(echo $COMPOSE_FAILED | wc -w | tr -d ' ')
|
||||
TOML_COUNT=$(echo $TOML_FAILED | wc -w | tr -d ' ')
|
||||
TOTAL=$(ls -d blueprints/*/ | wc -l | tr -d ' ')
|
||||
|
||||
{
|
||||
echo "## Blueprint audit"
|
||||
echo ""
|
||||
echo "| check | failing | of |"
|
||||
echo "| --- | --- | --- |"
|
||||
echo "| \`validate-docker-compose.ts\` | $COMPOSE_COUNT | $TOTAL |"
|
||||
echo "| \`validate-template.ts\` | $TOML_COUNT | $TOTAL |"
|
||||
echo ""
|
||||
} > "$SUMMARY"
|
||||
|
||||
if [ -f "$REPORT" ]; then
|
||||
cat "$REPORT" >> "$SUMMARY"
|
||||
else
|
||||
echo "No findings." >> "$SUMMARY"
|
||||
fi
|
||||
|
||||
# The step summary caps at ~1MB and truncates silently past it, which
|
||||
# would hide findings exactly as the backlog grows. Bound what we
|
||||
# append and point at the artifact for the rest.
|
||||
SUMMARY_LIMIT=$((512 * 1024))
|
||||
SUMMARY_BYTES=$(wc -c < "$SUMMARY" | tr -d ' ')
|
||||
|
||||
if [ "$SUMMARY_BYTES" -gt "$SUMMARY_LIMIT" ]; then
|
||||
# Emit the header table only, never a byte-sliced prefix: cutting
|
||||
# mid-``` leaves an unclosed code fence, which swallows the notice
|
||||
# below it and makes the truncation invisible in the rendered summary.
|
||||
{
|
||||
head -n 7 "$SUMMARY"
|
||||
echo ""
|
||||
echo "_Report omitted: $SUMMARY_BYTES bytes exceeds the $SUMMARY_LIMIT byte budget for a job summary._"
|
||||
echo "_Download the \`blueprint-audit\` artifact for the complete output._"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
cat "$SUMMARY" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
# Always published, so the full report survives regardless of size.
|
||||
cp "$SUMMARY" "$WORK/blueprint-audit.md"
|
||||
echo "report_path=$WORK/blueprint-audit.md" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "compose_failed=$COMPOSE_COUNT" >> "$GITHUB_OUTPUT"
|
||||
echo "toml_failed=$TOML_COUNT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$COMPOSE_COUNT" != "0" ] || [ "$TOML_COUNT" != "0" ]; then
|
||||
echo "::warning::$COMPOSE_COUNT compose and $TOML_COUNT template.toml findings across $TOTAL blueprints"
|
||||
fi
|
||||
|
||||
- name: Upload the full report
|
||||
if: always() && steps.audit.outputs.report_path != ''
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blueprint-audit
|
||||
path: ${{ steps.audit.outputs.report_path }}
|
||||
retention-days: 30
|
||||
|
||||
- name: Fail if requested
|
||||
# `inputs` is only populated for workflow_dispatch; on a schedule run it
|
||||
# is not available, so gate on the event first and read the value through
|
||||
# github.event.inputs, which is an empty string when absent.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
github.event.inputs.fail_on_findings == 'true' &&
|
||||
(steps.audit.outputs.compose_failed != '0' || steps.audit.outputs.toml_failed != '0')
|
||||
run: |
|
||||
echo "❌ Findings present and fail_on_findings was requested."
|
||||
exit 1
|
||||
@@ -5,6 +5,10 @@ on:
|
||||
workflows: [Build Preview Deployment]
|
||||
types:
|
||||
- completed
|
||||
pull_request:
|
||||
branches:
|
||||
- canary
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
deployments: write
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
name: Validate Docker Compose Files
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- canary
|
||||
paths:
|
||||
- 'blueprints/**/docker-compose.yml'
|
||||
- 'blueprints/**/template.toml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate-docker-compose:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Compose
|
||||
run: docker compose version
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.16.0
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd build-scripts && pnpm install
|
||||
|
||||
- name: Detect changed blueprints
|
||||
id: changed
|
||||
run: |
|
||||
BASE_SHA=$(git merge-base HEAD origin/${{ github.base_ref }})
|
||||
|
||||
# Obtener todos los blueprints que tienen cambios (en docker-compose.yml o template.toml)
|
||||
CHANGED_BLUEPRINTS=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA HEAD | \
|
||||
grep -E 'blueprints/[^/]+/(docker-compose\.yml|template\.toml)$' | \
|
||||
sed 's|blueprints/\([^/]*\)/.*|\1|' | \
|
||||
sort -u)
|
||||
|
||||
echo "Changed blueprints:"
|
||||
echo "$CHANGED_BLUEPRINTS" | while read blueprint; do
|
||||
[ -n "$blueprint" ] && echo " - $blueprint"
|
||||
done
|
||||
|
||||
# Guardar lista de blueprints (una por línea)
|
||||
echo "blueprints<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGED_BLUEPRINTS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Validate blueprints
|
||||
run: |
|
||||
BLUEPRINTS="${{ steps.changed.outputs.blueprints }}"
|
||||
|
||||
if [ -z "$BLUEPRINTS" ] || [ "$BLUEPRINTS" = "" ]; then
|
||||
echo "ℹ️ No blueprints changed, skipping validation"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ERROR=0
|
||||
|
||||
# Convertir a array para evitar problemas con subshells
|
||||
mapfile -t BLUEPRINT_ARRAY <<< "$BLUEPRINTS"
|
||||
|
||||
# Iterar sobre cada blueprint
|
||||
for blueprint in "${BLUEPRINT_ARRAY[@]}"; do
|
||||
[ -z "$blueprint" ] && continue
|
||||
|
||||
BLUEPRINT_PATH="blueprints/$blueprint"
|
||||
COMPOSE_FILE="$BLUEPRINT_PATH/docker-compose.yml"
|
||||
TOML_FILE="$BLUEPRINT_PATH/template.toml"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 Validating: $blueprint"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# 1. Validar best practices de docker-compose.yml
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "⚠️ WARNING: docker-compose.yml not found"
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
echo "🔍 Validating docker-compose.yml best practices..."
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-docker-compose.ts --file "../$COMPOSE_FILE" --verbose); then
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
|
||||
# 3. Validar template.toml
|
||||
if [ -f "$TOML_FILE" ]; then
|
||||
echo "🔍 Validating template.toml..."
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-template.ts --dir "../$BLUEPRINT_PATH" --verbose); then
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
else
|
||||
echo "⚠️ WARNING: template.toml not found"
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "✅ All validations passed for $blueprint"
|
||||
done
|
||||
|
||||
if [ $ERROR -eq 1 ]; then
|
||||
echo ""
|
||||
echo "❌ Validation failed for one or more blueprints"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "✅ All blueprints validated successfully"
|
||||
fi
|
||||
@@ -1,18 +1,12 @@
|
||||
name: Validate Template Metadata
|
||||
name: Validate and Process Meta.json
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [canary]
|
||||
paths:
|
||||
- "blueprints/**"
|
||||
- "build-scripts/generate-meta.js"
|
||||
- "meta.json"
|
||||
paths: ["meta.json"]
|
||||
pull_request:
|
||||
branches: [canary]
|
||||
paths:
|
||||
- "blueprints/**"
|
||||
- "build-scripts/generate-meta.js"
|
||||
- "meta.json"
|
||||
paths: ["meta.json"]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -23,19 +17,64 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Reject root meta.json
|
||||
run: |
|
||||
if [ -f "meta.json" ]; then
|
||||
echo "❌ The root meta.json is generated at build time and must not be committed."
|
||||
echo " Add your template's metadata to blueprints/<id>/meta.json instead."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ No root meta.json present"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "18"
|
||||
|
||||
- name: Validate per-template metadata
|
||||
run: node build-scripts/generate-meta.js --check
|
||||
- name: Validate meta.json structure
|
||||
run: |
|
||||
echo "🔍 Validating meta.json structure..."
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const data = JSON.parse(fs.readFileSync('meta.json', 'utf8'));
|
||||
if (!Array.isArray(data)) throw new Error('meta.json must be an array');
|
||||
console.log('✅ meta.json structure is valid');
|
||||
console.log('📊 Found', data.length, 'entries');
|
||||
"
|
||||
|
||||
- name: Check for duplicates and sort order
|
||||
run: |
|
||||
echo "🔍 Checking for duplicates and sort order..."
|
||||
node build-scripts/process-meta.js --verbose --output /tmp/meta-test.json
|
||||
|
||||
- name: Compare with original
|
||||
run: |
|
||||
echo "🔍 Comparing processed file with original..."
|
||||
if ! diff -q meta.json /tmp/meta-test.json > /dev/null; then
|
||||
echo "⚠️ meta.json needs processing (duplicates found or not sorted)"
|
||||
echo "Original entries:"
|
||||
node -e "console.log(JSON.parse(require('fs').readFileSync('meta.json', 'utf8')).length)"
|
||||
echo "Processed entries:"
|
||||
node -e "console.log(JSON.parse(require('fs').readFileSync('/tmp/meta-test.json', 'utf8')).length)"
|
||||
echo ""
|
||||
echo "To fix this, run: npm run process-meta"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ meta.json is properly deduplicated and sorted"
|
||||
fi
|
||||
|
||||
- name: Validate required fields
|
||||
run: |
|
||||
echo "🔍 Validating required fields..."
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const data = JSON.parse(fs.readFileSync('meta.json', 'utf8'));
|
||||
const required = ['id', 'name', 'version', 'description', 'links', 'logo', 'tags'];
|
||||
let issues = 0;
|
||||
|
||||
data.forEach((item, index) => {
|
||||
const missing = required.filter(field => !item[field]);
|
||||
if (missing.length > 0) {
|
||||
console.log('❌ Entry', index, '(' + item.id + '):', 'Missing fields:', missing.join(', '));
|
||||
issues++;
|
||||
}
|
||||
});
|
||||
|
||||
if (issues > 0) {
|
||||
console.log('🚨 Found', issues, 'entries with missing required fields');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('✅ All entries have required fields');
|
||||
}
|
||||
"
|
||||
|
||||
@@ -49,10 +49,133 @@ jobs:
|
||||
echo "✅ Blueprint folders validated successfully."
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- name: Validate meta.json structure and required fields
|
||||
run: |
|
||||
echo "🔍 Validating meta.json structure and required fields..."
|
||||
|
||||
- name: Validate per-template metadata (blueprints/<id>/meta.json)
|
||||
run: node build-scripts/generate-meta.js --check
|
||||
# First check if meta.json exists and is valid JSON
|
||||
if [ ! -f "meta.json" ]; then
|
||||
echo "❌ meta.json file not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! jq empty meta.json 2>/dev/null; then
|
||||
echo "❌ meta.json is not a valid JSON file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ERROR=0
|
||||
ERRORS_FOUND=""
|
||||
|
||||
# Debug: Show total number of entries
|
||||
TOTAL_ENTRIES=$(jq '. | length' meta.json)
|
||||
echo "📊 Total entries in meta.json: $TOTAL_ENTRIES"
|
||||
|
||||
# Get all entries at once and process them
|
||||
TOTAL_INDEX=$(($TOTAL_ENTRIES - 1))
|
||||
|
||||
for i in $(seq 0 $TOTAL_INDEX); do
|
||||
entry=$(jq -c ".[$i]" meta.json)
|
||||
INDEX=$((i + 1))
|
||||
|
||||
echo "-------------------------------------------"
|
||||
echo "🔍 Checking entry #$INDEX..."
|
||||
|
||||
# Get the ID for better error reporting
|
||||
ID=$(echo "$entry" | jq -r '.id // "UNKNOWN"')
|
||||
echo "📝 Processing entry with ID: $ID"
|
||||
|
||||
# Validate required top-level fields
|
||||
for field in "id" "name" "version" "description" "logo" "links" "tags"; do
|
||||
if [ "$(echo "$entry" | jq "has(\"$field\")")" != "true" ]; then
|
||||
ERROR_MSG="❌ Entry #$INDEX (ID: $ID) is missing required field: $field"
|
||||
echo "$ERROR_MSG"
|
||||
ERRORS_FOUND="${ERRORS_FOUND}${ERROR_MSG}\n"
|
||||
ERROR=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate links object required fields
|
||||
if [ "$(echo "$entry" | jq 'has("links")')" == "true" ]; then
|
||||
for link_field in "github" "website" "docs"; do
|
||||
if [ "$(echo "$entry" | jq ".links | has(\"$link_field\")")" != "true" ]; then
|
||||
ERROR_MSG="❌ Entry #$INDEX (ID: $ID): links object is missing required field: $link_field"
|
||||
echo "$ERROR_MSG"
|
||||
ERRORS_FOUND="${ERRORS_FOUND}${ERROR_MSG}\n"
|
||||
ERROR=1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Validate tags array is not empty
|
||||
if [ "$(echo "$entry" | jq '.tags | length')" -eq 0 ]; then
|
||||
ERROR_MSG="❌ Entry #$INDEX (ID: $ID): tags array cannot be empty"
|
||||
echo "$ERROR_MSG"
|
||||
ERRORS_FOUND="${ERRORS_FOUND}${ERROR_MSG}\n"
|
||||
ERROR=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "-------------------------------------------"
|
||||
if [ $ERROR -eq 1 ]; then
|
||||
echo "❌ meta.json structure validation failed."
|
||||
echo -e "Summary of all errors found:${ERRORS_FOUND}"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ meta.json structure validated successfully."
|
||||
fi
|
||||
|
||||
- name: Validate meta.json matches blueprint folders and logo files
|
||||
run: |
|
||||
echo "🔍 Validating meta.json against blueprint folders and logos..."
|
||||
|
||||
ERROR=0
|
||||
|
||||
# Read all blueprint folder names into an array
|
||||
FOLDERS=($(ls -1 blueprints))
|
||||
|
||||
# Extract ids and logos from meta.json
|
||||
IDS_AND_LOGOS=$(jq -c '.[] | {id, logo}' meta.json)
|
||||
|
||||
# Validate each id in meta.json exists as a folder
|
||||
for item in $IDS_AND_LOGOS; do
|
||||
ID=$(echo "$item" | jq -r '.id')
|
||||
LOGO=$(echo "$item" | jq -r '.logo')
|
||||
|
||||
# Check if folder exists
|
||||
if [ ! -d "blueprints/$ID" ]; then
|
||||
echo "❌ meta.json id \"$ID\" does not have a matching folder in blueprints/"
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if logo file exists inside its folder
|
||||
if [ ! -f "blueprints/$ID/$LOGO" ]; then
|
||||
echo "❌ Logo \"$LOGO\" defined for \"$ID\" does not exist in blueprints/$ID/"
|
||||
ERROR=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate each folder has a matching id in meta.json
|
||||
META_IDS=$(jq -r '.[].id' meta.json)
|
||||
for FOLDER in "${FOLDERS[@]}"; do
|
||||
FOUND=0
|
||||
for ID in $META_IDS; do
|
||||
if [ "$FOLDER" == "$ID" ]; then
|
||||
FOUND=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FOUND" -eq 0 ]; then
|
||||
echo "❌ Folder \"$FOLDER\" has no matching id in meta.json"
|
||||
ERROR=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $ERROR -eq 1 ]; then
|
||||
echo "❌ meta.json validation failed."
|
||||
exit 1
|
||||
else
|
||||
echo "✅ meta.json validated successfully."
|
||||
fi
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
meta.json.backup.*
|
||||
@@ -1,198 +0,0 @@
|
||||
# AGENTS.md: AI Collaboration Guide
|
||||
|
||||
This document provides essential context for AI models interacting with this project. Adhering to these guidelines will ensure consistency and maintain code quality.
|
||||
|
||||
## 1. Project Overview & Purpose
|
||||
|
||||
- **Primary Goal:** This is the official repository for Dokploy Open Source Templates, maintaining Docker Compose templates for deploying 200+ open-source applications via Dokploy (a self-hosted PaaS alternative to Heroku). The project enables rapid, standardized deployment of OSS applications without manual configuration.
|
||||
- **Business Domain:** Self-hosted Platform as a Service (PaaS), DevOps tooling, containerized application deployment, and open-source software distribution.
|
||||
|
||||
## 2. Core Technologies & Stack
|
||||
|
||||
- **Languages:** JavaScript (Node.js), TypeScript (React frontend), YAML (Docker Compose), TOML (template configuration), JSON (metadata), Shell (build scripts).
|
||||
- **Frameworks & Runtimes:** Node.js runtime, Vite 5.x (for React 19.x frontend), Docker Compose v3.8+, React Router 7.x.
|
||||
- **Databases:** No direct database usage in core project (templates may include various databases like PostgreSQL, MySQL, Redis, etc.).
|
||||
- **Key Libraries/Dependencies:**
|
||||
- **Core:** `nodemon` for development, custom Node.js scripts for meta processing.
|
||||
- **Frontend:** `fuse.js` (fuzzy search), `zustand` (state management), `@iarna/toml` (TOML parsing), `shadcn/ui` components, `@radix-ui` primitives, `@codemirror` (code editor), `react-router-dom`, `tailwindcss`.
|
||||
- **Build:** `vite-plugin-static-copy` for copying blueprints to dist.
|
||||
- **Platforms:** Linux (primary), Docker containers, Dokploy PaaS, Web browsers (for frontend preview), Cloudflare Pages (for PR previews).
|
||||
- **Package Manager:** npm for root project, pnpm for frontend (`app/` directory).
|
||||
|
||||
## 3. Architectural Patterns
|
||||
|
||||
- **Overall Architecture:** Template collection and management system with decentralized blueprint architecture. Each blueprint is a self-contained Docker Compose application with Dokploy-specific configuration.
|
||||
- **Directory Structure Philosophy:**
|
||||
- `/blueprints`: Contains all deployable application templates, each as a subdirectory with `docker-compose.yml`, `template.toml`, and logo files. Each blueprint is independent with no shared state.
|
||||
- `/app`: Vite + React + TypeScript frontend for local preview and development.
|
||||
- `/src/components`: React components (UI components in `/ui` subdirectory using shadcn/ui).
|
||||
- `/src/hooks`: Custom React hooks (e.g., `useFuseSearch.ts` for fuzzy search).
|
||||
- `/src/store`: Zustand state management store.
|
||||
- `/src/lib`: Utility functions and helpers.
|
||||
- `/build-scripts`: Metadata tooling — `generate-meta.js` (aggregates and validates every `blueprints/<id>/meta.json` into the served `meta.json` artifact) and `explode-meta.js` (one-shot migration reference).
|
||||
- `/.github/workflows`: CI/CD workflows for validation, preview builds, and deployments.
|
||||
- Root level: There is NO root `meta.json` in the repo — each template's metadata lives in `blueprints/<id>/meta.json` and the global registry is generated at build time.
|
||||
- **Module Organization:** Node.js scripts for metadata processing, React components for frontend UI, Docker Compose files for service definitions, TOML files for Dokploy configuration. Frontend uses component-based architecture with hooks for business logic and Zustand for global state.
|
||||
|
||||
## 4. Coding Conventions & Style Guide
|
||||
|
||||
- **Formatting:** JavaScript follows standard conventions with 2-space indentation. TypeScript in frontend uses ESLint configuration. YAML uses 2-space indentation. TOML files use standard formatting.
|
||||
- **Naming Conventions:**
|
||||
- Variables, functions: camelCase (`myVariable`, `myFunction`)
|
||||
- React components: PascalCase (`TemplateGrid`, `SearchBar`)
|
||||
- Constants: SCREAMING_SNAKE_CASE (`MAX_BUFFER_SIZE`)
|
||||
- Template IDs: lowercase, kebab-case (`activepieces`, `ghost`) - **MUST** be unique across repository
|
||||
- Files: kebab-case for scripts (`generate-meta.js`), PascalCase for React components (`TemplateGrid.tsx`), kebab-case for directories (`build-scripts`)
|
||||
- Docker services: **MUST** match blueprint folder name exactly (e.g., `ghost` service in `blueprints/ghost/`)
|
||||
- **API Design:**
|
||||
- **Backend:** Procedural scripting approach with CLI interfaces. Template system uses declarative configuration over imperative code.
|
||||
- **Frontend:** Component-based React architecture with hooks for logic separation. Zustand for centralized state management. Custom hooks encapsulate complex logic (e.g., `useFuseSearch` for search functionality).
|
||||
- **Common Patterns & Idioms:**
|
||||
- **Metaprogramming:** Minimal use of advanced JavaScript features in scripts, focusing on simple, maintainable code.
|
||||
- **Memory Management:** Relies on Node.js garbage collection and React's automatic memory management.
|
||||
- **Polymorphism:** Uses JavaScript prototype-based objects and functional programming patterns. React components use composition over inheritance.
|
||||
- **Type Safety:**
|
||||
- Backend scripts: JavaScript with JSDoc comments for documentation.
|
||||
- Frontend: TypeScript with strict type checking enabled.
|
||||
- **Concurrency:**
|
||||
- Backend: Synchronous processing model for meta.json operations.
|
||||
- Frontend: React's concurrent rendering, `useDeferredValue` for debouncing, `useEffect` for async operations.
|
||||
- **State Management:** Zustand store pattern with selectors for optimal re-renders. URL params synced with search state via React Router.
|
||||
- **Error Handling:**
|
||||
- Backend: Node.js error-first callback pattern and try-catch blocks. Scripts validate JSON structure and fail fast on errors.
|
||||
- Frontend: Error boundaries for React components, try-catch for async operations, user-friendly error messages via toast notifications.
|
||||
|
||||
## 5. Key Files & Entrypoints
|
||||
|
||||
- **Main Entrypoints:**
|
||||
- **Backend:** `build-scripts/generate-meta.js` - Aggregates and validates all `blueprints/<id>/meta.json` files into the served `meta.json` artifact (`app/public/meta.json`, gitignored).
|
||||
- **Frontend:** `app/src/main.tsx` - React application entry point.
|
||||
- **Configuration:**
|
||||
- `app/package.json` - Frontend dependencies and pnpm scripts (`dev` and `build` run `generate-meta.js` first).
|
||||
- `blueprints/<id>/meta.json` - Per-template metadata (one object per template; 400+ templates).
|
||||
- `app/vite.config.ts` - Vite build configuration with static copy plugin.
|
||||
- `app/tsconfig.json` - TypeScript compiler configuration.
|
||||
- **CI/CD Pipeline:**
|
||||
- `.github/workflows/validate-meta.yml` - Validates every `blueprints/<id>/meta.json` (via `generate-meta.js --check`) and rejects a committed root `meta.json`.
|
||||
- `.github/workflows/build-preview.yml` - Builds preview deployments for PRs.
|
||||
- `.github/workflows/deploy-preview.yml` - Deploys previews to Cloudflare Pages.
|
||||
|
||||
## 6. Development & Testing Workflow
|
||||
|
||||
- **Local Development Environment:**
|
||||
- **Install dependencies**
|
||||
|
||||
```bash
|
||||
# Root project
|
||||
npm install
|
||||
|
||||
# Frontend (uses pnpm)
|
||||
cd app && pnpm install
|
||||
```
|
||||
|
||||
- **Validate template metadata** (CRITICAL: run after adding/editing any `blueprints/<id>/meta.json`)
|
||||
```bash
|
||||
node build-scripts/generate-meta.js --check
|
||||
```
|
||||
- **Generate the served meta.json artifact** (done automatically by `pnpm dev` / `pnpm build` in `app/`)
|
||||
```bash
|
||||
node build-scripts/generate-meta.js # writes app/public/meta.json
|
||||
```
|
||||
- **Task Configuration:**
|
||||
- **App scripts (`app/package.json`, pnpm):**
|
||||
- `dev`: Generate meta artifact + start Vite dev server
|
||||
- `build`: Generate meta artifact + typecheck + Vite build
|
||||
- `generate-meta`: Generate the meta artifact only
|
||||
- **Testing:** No formal unit testing framework. Validation occurs through:
|
||||
- Script-based validation of every `blueprints/<id>/meta.json` (`build-scripts/generate-meta.js --check`)
|
||||
- Manual testing via Dokploy preview deployments (import base64 from PR preview)
|
||||
- TypeScript compilation errors caught during build
|
||||
- ESLint for code quality in frontend
|
||||
- **CI/CD Process:**
|
||||
- **On blueprint changes:** Validates each template's metadata (required fields, id/folder match, logo file exists) and rejects a committed root `meta.json`.
|
||||
- **On PR creation:** Builds preview deployment, generates base64 import for testing in Dokploy.
|
||||
- **Preview testing:** Use PR description link → Search template → Copy base64 → Import in Dokploy instance.
|
||||
|
||||
## 7. Specific Instructions for AI Collaboration
|
||||
|
||||
- **Contribution Guidelines:**
|
||||
- Follow existing Dokploy template structure strictly. Each blueprint must be independent with no shared state.
|
||||
- **CRITICAL:** Template metadata lives in `blueprints/<id>/meta.json` (one object per template). Never create or edit a root-level `meta.json` — it is generated at build time. Run `node build-scripts/generate-meta.js --check` after any metadata edits.
|
||||
- Test templates in Dokploy preview before submitting PRs (use base64 import from PR preview).
|
||||
- Add logo file (SVG preferred, ~128x128px) to blueprint folder.
|
||||
- Ensure the `id` in `blueprints/<id>/meta.json` exactly matches the blueprint folder name (lowercase kebab-case).
|
||||
- **Docker Compose Conventions (CRITICAL):**
|
||||
|
||||
- **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):**
|
||||
```yaml
|
||||
services:
|
||||
ghost:
|
||||
image: ghost:6-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- ghost:/var/lib/ghost/content
|
||||
volumes:
|
||||
ghost:
|
||||
```
|
||||
|
||||
- **template.toml Conventions:**
|
||||
|
||||
- **Variables:** Define in `[variables]` section, use helpers for secrets
|
||||
- **Domains:** `[[config.domains]]` with `serviceName`, `port`, `host` (path optional)
|
||||
- **Env:** Array of strings: `env = ["KEY=VALUE", "DB_PASSWORD=${db_pass}"]`
|
||||
- **Available helpers:** `${domain}`, `${password:length}`, `${base64:length}`, `${hash:length}`, `${uuid}`, `${randomPort}`, `${email}`, `${username}`, `${timestamp}`, `${timestamps:datetime}`, `${timestampms:datetime}`, `${jwt:secret_var:payload_var}`
|
||||
- **JWT helper example:** `${jwt:mysecret:mypayload}` with payload containing `exp: ${timestamps:2030-01-01T00:00:00Z}`
|
||||
- **Volume bind mounts (in docker-compose.yml):** When mounting host paths, do NOT use absolute paths like `"/folder:/path/in/container"`. Use relative paths instead:
|
||||
```yaml
|
||||
# Invalid
|
||||
volumes:
|
||||
- "/folder:/path/in/container" ❌
|
||||
# Valid
|
||||
volumes:
|
||||
- "../files/my-database:/var/lib/mysql" ✅
|
||||
- "../files/my-configs:/etc/my-app/config" ✅
|
||||
```
|
||||
|
||||
- **meta.json Requirements:**
|
||||
|
||||
- **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).
|
||||
- **Logo:** Filename only (e.g., `"ghost.jpeg"`), file must exist in blueprint folder
|
||||
|
||||
- **Frontend Development:**
|
||||
|
||||
- Use TypeScript with strict type checking
|
||||
- Follow React hooks patterns, avoid class components
|
||||
- Use Zustand selectors for state access to optimize re-renders
|
||||
- Fuse.js searches across `name`, `description`, `tags`, `id` fields
|
||||
- Use shadcn/ui components for consistency
|
||||
- Sync URL params with search state via React Router
|
||||
|
||||
- **Security:**
|
||||
|
||||
- Be mindful of security when handling template configurations
|
||||
- NEVER hardcode secrets in templates - use Dokploy's variable system with helpers
|
||||
- Pin Docker images to specific versions to avoid supply chain attacks
|
||||
- Validate user input in frontend before processing
|
||||
|
||||
- **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)
|
||||
- For frontend dependencies: Use `pnpm add <package>` in `app/` directory
|
||||
- For root dependencies: Use `npm install <package>` in root directory
|
||||
|
||||
- **Commit Messages:** Follow conventional commit patterns (e.g., `feat:`, `fix:`, `docs:`, `chore:`).
|
||||
|
||||
- **Common Pitfalls to Avoid:**
|
||||
1. Forgetting to process meta.json after editing (CI will fail)
|
||||
2. Template ID mismatch between meta.json and folder name
|
||||
3. Including `ports`, `container_name`, or `networks` in docker-compose.yml
|
||||
4. Using object syntax for env vars in template.toml (must be array of strings)
|
||||
5. Logo file missing or filename mismatch in meta.json
|
||||
6. Version mismatch between meta.json and docker-compose.yml image tag
|
||||
@@ -20,11 +20,6 @@ This project adheres to the [Contributor Covenant Code of Conduct](https://www.c
|
||||
|
||||
For larger changes or questions, open an issue first to discuss your ideas.
|
||||
|
||||
### Pull Request Guidelines
|
||||
|
||||
- **Keep PRs small and focused.** Avoid very large PRs; prefer several smaller PRs (e.g., one template or one logical change per PR). This speeds up review and keeps the history clear.
|
||||
- **Test before submitting.** Any PR that has not been tested by the contributor will be closed. This keeps the PR queue tidy and ensures that only contributions that have been verified by their authors are considered.
|
||||
|
||||
## Adding a New Template
|
||||
|
||||
To add a new template, follow these steps:
|
||||
@@ -32,11 +27,13 @@ To add a new template, follow these steps:
|
||||
1. **Create a Folder in `blueprints/`**: Name it after the template (e.g., `grafana`). Use lowercase with hyphens for multi-word names.
|
||||
2. **Add `docker-compose.yml`**: Define the services, volumes, and other Compose configurations. Key guidelines:
|
||||
|
||||
- Use `version: "3.8"` or later.
|
||||
- Avoid exposing ports explicitly (e.g., no `ports: - "3000:3000"`; just `- 3000` if needed for internal reference).
|
||||
- Do not use `container_name`.
|
||||
- Avoid custom networks like `dokploy-network`; Dokploy handles isolation automatically.
|
||||
- Example for Grafana:
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
grafana:
|
||||
image: grafana/grafana-enterprise:9.5.20
|
||||
@@ -71,16 +68,16 @@ To add a new template, follow these steps:
|
||||
"""
|
||||
```
|
||||
|
||||
4. **Add `meta.json` in your template folder**: Create `blueprints/<id>/meta.json` with a single JSON object describing your template. Do NOT create or edit a root-level `meta.json` — the global registry is generated automatically at build time from every `blueprints/<id>/meta.json` (this is what makes template PRs conflict-free). Include:
|
||||
4. **Update `meta.json`**: Add an entry for your template in the root `meta.json`. Include:
|
||||
|
||||
- `id`: Folder name (e.g., "grafana") — must match the folder exactly.
|
||||
- `id`: Folder name (e.g., "grafana").
|
||||
- `name`: Display name.
|
||||
- `version`: Image/tag version.
|
||||
- `description`: Brief overview.
|
||||
- `logo`: Filename of the logo (e.g., "grafana.svg").
|
||||
- `links`: Object with `github`, `website`, `docs` (URLs; use `""` if not applicable).
|
||||
- `links`: Object with `github`, `website`, `docs` (URLs).
|
||||
- `tags`: Array of categories (e.g., ["monitoring"]).
|
||||
- Example (`blueprints/grafana/meta.json`):
|
||||
- Example:
|
||||
```json
|
||||
{
|
||||
"id": "grafana",
|
||||
@@ -99,7 +96,7 @@ To add a new template, follow these steps:
|
||||
|
||||
5. **Add a Logo**: Place an SVG or PNG logo (e.g., `grafana.svg`) in the template folder. Keep it simple and representative (ideally 512x512 or similar).
|
||||
|
||||
6. **Run Validation**: Before pushing, run `node build-scripts/generate-meta.js --check` to validate every template's metadata (same check CI runs), and manually check for syntax errors in YAML/TOML.
|
||||
6. **Run Validation**: Before pushing, run `node dedupe-and-sort-meta.js` (if available) or manually check for syntax errors in YAML/TOML/JSON.
|
||||
|
||||
7. **Commit and PR**: Push your branch and open a PR. Include:
|
||||
- A description of the template.
|
||||
@@ -151,6 +148,7 @@ Use these in `${}` for dynamic values:
|
||||
|
||||
- Omit explicit ports; let Dokploy handle exposure.
|
||||
- Use persistent volumes for data (e.g., databases).
|
||||
- Set `restart: unless-stopped` for services.
|
||||
|
||||
- **Template.toml**:
|
||||
|
||||
@@ -193,7 +191,7 @@ If issues arise, debug locally or in the preview. Fix and update your PR.
|
||||
## Updating Existing Templates
|
||||
|
||||
- Follow the same structure as adding new ones.
|
||||
- Bump `version` in the template's `blueprints/<id>/meta.json` and update `docker-compose.yml` image tags.
|
||||
- Bump `version` in `meta.json` and update `docker-compose.yml` image tags.
|
||||
- Test thoroughly, as changes may affect users.
|
||||
|
||||
## Reporting Bugs or Requesting Features
|
||||
|
||||
@@ -8,7 +8,7 @@ This is the official repository for the Dokploy Open Source Templates.
|
||||
1. Fork the repository
|
||||
2. Create a new branch
|
||||
3. Add the template to the `blueprints` folder (`docker-compose.yml`, `template.toml`)
|
||||
4. Add the template metadata (name, description, version, logo, links, tags) in a `meta.json` file inside your template folder (`blueprints/<id>/meta.json`) — do NOT create or edit a root-level `meta.json`, it is generated at build time
|
||||
4. Add the template metadata (name, description, version, logo, links, tags) to the `meta.json` file
|
||||
5. Add the logo to the template folder
|
||||
6. Commit and push your changes
|
||||
7. Create a pull request (PR)
|
||||
@@ -35,6 +35,7 @@ Let's suppose you want to add the [Grafana](https://grafana.com/) template to th
|
||||
2. Add the `docker-compose.yml` file to the folder
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
grafana:
|
||||
image: grafana/grafana-enterprise:9.5.20
|
||||
@@ -61,7 +62,7 @@ host = "${main_domain}"
|
||||
|
||||
[[config.mounts]]
|
||||
```
|
||||
4. Add the meta information in `blueprints/<id>/meta.json` (a single JSON object; the root `meta.json` is generated automatically at build time)
|
||||
4. Add meta information to the `meta.json` file in the root folder
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -169,7 +170,7 @@ We have a few helpers that are very common when creating a template, these are:
|
||||
|
||||
|
||||
|
||||
## General Requirements when creating a template
|
||||
## General Suggestions when creating a template
|
||||
|
||||
- Don't use this way in your docker compose file:
|
||||
|
||||
@@ -182,7 +183,7 @@ services:
|
||||
- 3000:3000
|
||||
|
||||
# Instead use this way:
|
||||
expose:
|
||||
ports:
|
||||
- 3000
|
||||
```
|
||||
|
||||
@@ -224,4 +225,4 @@ services:
|
||||
6. Now you can click on the Deploy Button and wait for the deployment to finish, and try to access to the service, if everything is correct you should access to the service and see the template working.
|
||||
|
||||
|
||||
Each template's metadata lives in `blueprints/<id>/meta.json`; the global `meta.json` is generated at build time. Use `node build-scripts/generate-meta.js --check` to validate all template metadata.
|
||||
use the command `node dedupe-and-sort-meta.js` to deduplicate and sort the meta.json file.
|
||||
@@ -4,16 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="canonical" href="https://dokploy.com/templates" />
|
||||
<meta name="description" content="Browse 500+ open source Docker templates and one-click self-hosted apps to deploy with Dokploy – databases, CMS, analytics, AI tools and more." />
|
||||
<meta property="og:title" content="Dokploy Templates: 500+ Open Source Apps" />
|
||||
<meta property="og:description" content="Browse 500+ open source Docker templates and one-click self-hosted apps to deploy with Dokploy – databases, CMS, analytics, AI tools and more." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://templates.dokploy.com" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Dokploy Templates: 500+ Open Source Apps" />
|
||||
<meta name="twitter:description" content="Browse 500+ open source Docker templates and one-click self-hosted apps to deploy with Dokploy – databases, CMS, analytics, AI tools and more." />
|
||||
<title>Dokploy Templates: 500+ Open Source Apps</title>
|
||||
<title>Dokploy Blueprints</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node ../build-scripts/generate-meta.js && vite",
|
||||
"build": "node ../build-scripts/generate-meta.js && tsc -b && vite build",
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"generate-meta": "node ../build-scripts/generate-meta.js"
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.19.1",
|
||||
@@ -35,13 +34,13 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.18.0",
|
||||
"react-router-dom": "^7.9.4",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vite-plugin-static-copy": "^2.3.2",
|
||||
"yaml": "2.8.3",
|
||||
"yaml": "2.7.1",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -51,6 +50,6 @@
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"globals": "^15.15.0",
|
||||
"typescript": "~5.7.3",
|
||||
"vite": "^6.4.3"
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
- .
|
||||
allowBuilds:
|
||||
esbuild: false
|
||||
@@ -1 +0,0 @@
|
||||
meta.json
|
||||
@@ -13,9 +13,7 @@ const Navigation = () => {
|
||||
"https://api.github.com/repos/dokploy/dokploy"
|
||||
);
|
||||
const data = await response.json();
|
||||
setGithubStars(
|
||||
Number.isFinite(data.stargazers_count) ? data.stargazers_count : 0
|
||||
);
|
||||
setGithubStars(data.stargazers_count);
|
||||
} catch (error) {
|
||||
console.error("Error fetching GitHub stars:", error);
|
||||
}
|
||||
|
||||
@@ -109,22 +109,20 @@ const Search = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
className="w-full"
|
||||
/>
|
||||
{searchQuery.length > 0 ? (
|
||||
<div className="cursor-pointer" onClick={handleClearSearch}>
|
||||
<XIcon className="absolute end-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
) : (
|
||||
<SearchIcon className="absolute end-3 top-1/2 -translate-y-1/2 lg:size-5 size-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
className="w-full p-6"
|
||||
/>
|
||||
{searchQuery.length > 0 ? (
|
||||
<div className="cursor-pointer" onClick={handleClearSearch}>
|
||||
<XIcon className="absolute end-3 translate-y-3.5 top-1/2 h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
) : (
|
||||
<SearchIcon className="absolute end-3 translate-y-2 top-1/2 lg:size-5 size-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2 justify-between">
|
||||
@@ -156,7 +154,7 @@ const Search = () => {
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-50 p-0">
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Search tags...."
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
|
||||
import { Label } from "./ui/label";
|
||||
import { Clipboard } from "lucide-react";
|
||||
import { Input } from "./ui/input";
|
||||
import Markdown from "./ui/markdown";
|
||||
|
||||
interface Template {
|
||||
id: string;
|
||||
@@ -33,7 +32,6 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateDialogProps {
|
||||
@@ -171,23 +169,8 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs
|
||||
defaultValue={
|
||||
templateFiles?.instructions
|
||||
? "instructions"
|
||||
: "docker-compose"
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
<Tabs defaultValue="docker-compose" className="w-full">
|
||||
<TabsList className="w-full justify-start">
|
||||
{templateFiles?.instructions && (
|
||||
<TabsTrigger
|
||||
value="instructions"
|
||||
className="data-[state=active]:font-bold"
|
||||
>
|
||||
Instructions
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger
|
||||
value="docker-compose"
|
||||
className="data-[state=active]:font-bold"
|
||||
@@ -201,24 +184,6 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
Configuration
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{templateFiles?.instructions && (
|
||||
<TabsContent value="instructions" className="mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="flex flex-col items-start w-fit justify-start gap-1">
|
||||
<span className="leading-tight text-xl font-semibold">
|
||||
Setup Instructions
|
||||
</span>
|
||||
<span className="leading-tight text-sm text-gray-500">
|
||||
instructions.md
|
||||
</span>
|
||||
</Label>
|
||||
<div className="rounded-md border p-4">
|
||||
<Markdown content={templateFiles.instructions} />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
<TabsContent value="docker-compose" className="mt-4">
|
||||
{templateFiles?.dockerCompose && (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -28,7 +28,6 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateGridProps {
|
||||
@@ -81,34 +80,20 @@ const TemplateGrid: React.FC<TemplateGridProps> = ({ view }) => {
|
||||
const fetchTemplateFiles = async (templateId: string) => {
|
||||
setModalLoading(true);
|
||||
try {
|
||||
const [dockerComposeRes, configRes, instructionsRes] = await Promise.all(
|
||||
[
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
fetch(`/blueprints/${templateId}/instructions.md`),
|
||||
]
|
||||
);
|
||||
const [dockerComposeRes, configRes] = await Promise.all([
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
]);
|
||||
|
||||
const dockerCompose = dockerComposeRes.ok
|
||||
? await dockerComposeRes.text()
|
||||
: null;
|
||||
const config = configRes.ok ? await configRes.text() : null;
|
||||
|
||||
// Guard against SPA fallbacks that return index.html with a 200 status
|
||||
// for templates that don't have an instructions.md file.
|
||||
const instructionsIsMarkdown =
|
||||
instructionsRes.ok &&
|
||||
!(instructionsRes.headers.get("content-type") || "").includes(
|
||||
"text/html"
|
||||
);
|
||||
const instructions = instructionsIsMarkdown
|
||||
? await instructionsRes.text()
|
||||
: null;
|
||||
|
||||
setTemplateFiles({ dockerCompose, config, instructions });
|
||||
setTemplateFiles({ dockerCompose, config });
|
||||
} catch (err) {
|
||||
console.error("Error fetching template files:", err);
|
||||
setTemplateFiles({ dockerCompose: null, config: null, instructions: null });
|
||||
setTemplateFiles({ dockerCompose: null, config: null });
|
||||
} finally {
|
||||
setModalLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
// Minimal markdown renderer (no external dependencies) for the per-template
|
||||
// instructions.md files. Supports headings, paragraphs, fenced code blocks,
|
||||
// ordered/unordered lists, and inline code / bold / links.
|
||||
|
||||
const INLINE_PATTERN =
|
||||
/(`[^`]+`)|(\*\*[^*]+\*\*)|(\[[^\]]+\]\([^)\s]+\))/g;
|
||||
|
||||
const renderInline = (text: string): React.ReactNode[] => {
|
||||
const nodes: React.ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
let key = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
INLINE_PATTERN.lastIndex = 0;
|
||||
|
||||
while ((match = INLINE_PATTERN.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
nodes.push(text.slice(lastIndex, match.index));
|
||||
}
|
||||
const token = match[0];
|
||||
if (token.startsWith("`")) {
|
||||
nodes.push(
|
||||
<code
|
||||
key={key++}
|
||||
className="px-1.5 py-0.5 rounded bg-muted font-mono text-[0.85em]"
|
||||
>
|
||||
{token.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
} else if (token.startsWith("**")) {
|
||||
nodes.push(<strong key={key++}>{token.slice(2, -2)}</strong>);
|
||||
} else {
|
||||
const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(token);
|
||||
if (link) {
|
||||
nodes.push(
|
||||
<a
|
||||
key={key++}
|
||||
href={link[2]}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 dark:text-blue-400 underline underline-offset-2"
|
||||
>
|
||||
{link[1]}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
nodes.push(token);
|
||||
}
|
||||
}
|
||||
lastIndex = match.index + token.length;
|
||||
}
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(text.slice(lastIndex));
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
|
||||
const HEADING_CLASSES: Record<number, string> = {
|
||||
1: "text-2xl font-bold mt-2",
|
||||
2: "text-xl font-semibold mt-6 border-b pb-1",
|
||||
3: "text-lg font-semibold mt-4",
|
||||
4: "text-base font-semibold mt-3",
|
||||
};
|
||||
|
||||
interface MarkdownProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Markdown: React.FC<MarkdownProps> = ({ content }) => {
|
||||
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
||||
const blocks: React.ReactNode[] = [];
|
||||
let key = 0;
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
|
||||
// Blank line
|
||||
if (line.trim() === "") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fenced code block
|
||||
if (line.trim().startsWith("```")) {
|
||||
const code: string[] = [];
|
||||
i++;
|
||||
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
||||
code.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
i++; // skip closing fence
|
||||
blocks.push(
|
||||
<pre
|
||||
key={key++}
|
||||
className="bg-muted rounded-md p-4 overflow-x-auto text-sm font-mono whitespace-pre"
|
||||
>
|
||||
<code>{code.join("\n")}</code>
|
||||
</pre>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Heading
|
||||
const heading = /^(#{1,4})\s+(.*)$/.exec(line);
|
||||
if (heading) {
|
||||
const level = heading[1].length;
|
||||
const Tag = `h${Math.min(level + 1, 6)}` as keyof React.JSX.IntrinsicElements;
|
||||
blocks.push(
|
||||
<Tag key={key++} className={HEADING_CLASSES[level]}>
|
||||
{renderInline(heading[2])}
|
||||
</Tag>
|
||||
);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unordered list
|
||||
if (/^[-*]\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^[-*]\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ul key={key++} className="list-disc pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ordered list
|
||||
if (/^\d+\.\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^\d+\.\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ol key={key++} className="list-decimal pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Paragraph: consume consecutive plain lines
|
||||
const paragraph: string[] = [line];
|
||||
i++;
|
||||
while (
|
||||
i < lines.length &&
|
||||
lines[i].trim() !== "" &&
|
||||
!/^(#{1,4})\s+/.test(lines[i]) &&
|
||||
!/^[-*]\s+/.test(lines[i]) &&
|
||||
!/^\d+\.\s+/.test(lines[i]) &&
|
||||
!lines[i].trim().startsWith("```")
|
||||
) {
|
||||
paragraph.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<p key={key++} className="leading-relaxed">
|
||||
{renderInline(paragraph.join(" "))}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="space-y-3 text-sm">{blocks}</div>;
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "./components/ui/dropdown-menu"
|
||||
import { useTheme } from "./theme-provider"
|
||||
|
||||
@@ -13,7 +13,7 @@ export function ModeToggle() {
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
|
||||
@@ -11,9 +11,11 @@ export default defineConfig({
|
||||
{
|
||||
src: '../blueprints/*',
|
||||
dest: 'blueprints' // raíz de dist (public root)
|
||||
},
|
||||
{
|
||||
src: '../meta.json',
|
||||
dest: '' // raíz de dist
|
||||
}
|
||||
// meta.json is no longer copied from the repo root: it is
|
||||
// generated into app/public/meta.json by build-scripts/generate-meta.js
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#101a3a"/><stop offset="1" stop-color="#050913"/></linearGradient>
|
||||
<linearGradient id="rim" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#7c6cff"/><stop offset="1" stop-color="#2de2ff"/></linearGradient>
|
||||
</defs>
|
||||
<rect x="6" y="6" width="500" height="500" rx="116" fill="url(#bg)" stroke="url(#rim)" stroke-width="12"/>
|
||||
<polygon points="256.0,181.5 442.0,288.8 256.0,396.2 70.0,288.8" fill="#2de2ff" fill-opacity="0.12" stroke="#2de2ff" stroke-width="9" stroke-linejoin="round"/>
|
||||
<polygon points="195.5,237.8 256.0,272.7 256.0,197.6 195.5,162.7" fill="#4f3fd0"/>
|
||||
<polygon points="316.5,237.8 256.0,272.7 256.0,197.6 316.5,162.7" fill="#7c6cff"/>
|
||||
<polygon points="256.0,127.8 316.5,162.7 256.0,197.6 195.5,162.7" fill="#b3abff"/>
|
||||
<line x1="208.8" y1="219.2" x2="242.7" y2="238.8" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
|
||||
<line x1="208.8" y1="194.4" x2="242.7" y2="213.9" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
|
||||
<polygon points="283.9,288.8 344.3,323.7 344.3,211.0 283.9,176.1" fill="#0f9cbb"/>
|
||||
<polygon points="404.8,288.8 344.3,323.7 344.3,211.0 404.8,176.1" fill="#2de2ff"/>
|
||||
<polygon points="344.4,141.2 404.8,176.1 344.3,211.0 283.9,176.1" fill="#b3f6ff"/>
|
||||
<line x1="297.2" y1="257.1" x2="331.1" y2="276.6" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
|
||||
<line x1="297.2" y1="219.9" x2="331.1" y2="239.4" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
|
||||
<polygon points="195.6,339.9 256.0,374.8 256.0,315.7 195.6,280.8" fill="#2856c8"/>
|
||||
<polygon points="316.4,339.9 256.0,374.8 256.0,315.7 316.4,280.8" fill="#4f8bff"/>
|
||||
<polygon points="256.0,245.9 316.4,280.8 256.0,315.7 195.6,280.8" fill="#a8c8ff"/>
|
||||
<line x1="208.8" y1="318.0" x2="242.7" y2="337.5" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
3dokploy:
|
||||
image: ghcr.io/suleyman/3dokploy:0.1.1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DOKPLOY_URL=${DOKPLOY_URL}
|
||||
- DOKPLOY_API_KEY=${DOKPLOY_API_KEY}
|
||||
- BASIC_AUTH=${BASIC_AUTH}
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"id": "3dokploy",
|
||||
"name": "3Dokploy",
|
||||
"version": "0.1.1",
|
||||
"description": "A live, isometric 3D view of your Dokploy: projects as buildings, services as racks, deploys rolling in on carts, network traffic as packets. Read-only.",
|
||||
"logo": "3dokploy.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/suleyman/3dokploy",
|
||||
"website": "",
|
||||
"docs": "https://github.com/suleyman/3dokploy#readme"
|
||||
},
|
||||
"tags": ["monitoring", "dashboard", "devops"]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
auth_password = "${password:24}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "3dokploy"
|
||||
port = 3_000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
# The Dokploy on this same server. If its port 3000 is closed, use its public URL instead.
|
||||
DOKPLOY_URL = "http://host.docker.internal:3000"
|
||||
# Required: an API key from Settings → Profile → API/CLI. It stays on the server.
|
||||
DOKPLOY_API_KEY = ""
|
||||
# Site login (user admin).
|
||||
BASIC_AUTH = "admin:${auth_password}"
|
||||
|
Before Width: | Height: | Size: 105 KiB |
@@ -1,44 +0,0 @@
|
||||
services:
|
||||
9router:
|
||||
image: decolua/9router:0.5.59
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DATA_DIR: /app/data
|
||||
PORT: "20128"
|
||||
HOSTNAME: "0.0.0.0"
|
||||
NODE_ENV: production
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
INITIAL_PASSWORD: ${INITIAL_PASSWORD}
|
||||
API_KEY_SECRET: ${API_KEY_SECRET}
|
||||
MACHINE_ID_SALT: ${MACHINE_ID_SALT}
|
||||
BASE_URL: https://${ROUTER_HOST}
|
||||
NEXT_PUBLIC_BASE_URL: https://${ROUTER_HOST}
|
||||
HEADROOM_URL: http://headroom:8787
|
||||
AUTH_COOKIE_SECURE: "true"
|
||||
REQUIRE_API_KEY: "true"
|
||||
volumes:
|
||||
- 9router-data:/app/data
|
||||
depends_on:
|
||||
headroom:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:20128/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# Optional token-reduction proxy that 9Router calls through HEADROOM_URL.
|
||||
# Shipped as a sidecar because the 9Router image does not bundle it.
|
||||
headroom:
|
||||
image: ghcr.io/chopratejas/headroom:0.6.7
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8787/health')"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
9router-data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "9router",
|
||||
"name": "9Router",
|
||||
"version": "0.5.59",
|
||||
"description": "Self-hosted AI model router that rotates requests across 40+ providers with automatic fallback, exposing an OpenAI-compatible endpoint for coding agents.",
|
||||
"logo": "9router.png",
|
||||
"links": {
|
||||
"github": "https://github.com/decolua/9router",
|
||||
"website": "https://9router.com",
|
||||
"docs": "https://github.com/decolua/9router/blob/master/DOCKER.md"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"proxy",
|
||||
"developer-tools"
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
jwt_secret = "${password:64}"
|
||||
initial_password = "${password:32}"
|
||||
api_key_secret = "${password:64}"
|
||||
machine_id_salt = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"ROUTER_HOST=${main_domain}",
|
||||
"JWT_SECRET=${jwt_secret}",
|
||||
"INITIAL_PASSWORD=${initial_password}",
|
||||
"API_KEY_SECRET=${api_key_secret}",
|
||||
"MACHINE_ID_SALT=${machine_id_salt}",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "9router"
|
||||
port = 20128
|
||||
host = "${main_domain}"
|
||||
@@ -1,13 +1,14 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
ackee:
|
||||
image: electerious/ackee:3.4.2
|
||||
expose:
|
||||
ports:
|
||||
- "3000"
|
||||
environment:
|
||||
- ACKEE_USERNAME=${ACKEE_USERNAME}
|
||||
- ACKEE_PASSWORD=${ACKEE_PASSWORD}
|
||||
- ACKEE_MONGODB=${ACKEE_MONGODB}
|
||||
|
||||
mongo:
|
||||
image: mongo:4
|
||||
environment:
|
||||
@@ -16,7 +17,6 @@ services:
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "ackee",
|
||||
"name": "Ackee",
|
||||
"version": "latest",
|
||||
"description": "Ackee is a self-hosted analytics tool for your website.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/electerious/Ackee",
|
||||
"website": "https://ackee.electerious.com/",
|
||||
"docs": "https://docs.ackee.electerious.com/"
|
||||
},
|
||||
"tags": [
|
||||
"analytics",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -10,7 +10,6 @@ serviceName = "ackee"
|
||||
port = 3000
|
||||
host = "${domain}"
|
||||
|
||||
|
||||
[config.env]
|
||||
ACKEE_USERNAME = "${ACKEE_USERNAME}"
|
||||
ACKEE_PASSWORD = "${ACKEE_PASSWORD}"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
activepieces:
|
||||
image: activepieces/activepieces:0.91.2
|
||||
image: activepieces/activepieces:0.35.0
|
||||
restart: unless-stopped
|
||||
|
||||
depends_on:
|
||||
@@ -30,7 +32,7 @@ services:
|
||||
AP_TEMPLATES_SOURCE_URL: https://cloud.activepieces.com/api/v1/flow-templates
|
||||
|
||||
postgres:
|
||||
image: postgres:17
|
||||
image: postgres:14
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
@@ -59,4 +61,4 @@ services:
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
redis_data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "activepieces",
|
||||
"name": "Activepieces",
|
||||
"version": "0.91.2",
|
||||
"description": "Open-source no-code business automation tool. An alternative to Zapier, Make.com, and Tray.",
|
||||
"logo": "activepieces.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/activepieces/activepieces",
|
||||
"website": "https://www.activepieces.com/",
|
||||
"docs": "https://www.activepieces.com/docs"
|
||||
},
|
||||
"tags": [
|
||||
"automation",
|
||||
"workflow",
|
||||
"no-code"
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "actualbudget",
|
||||
"name": "Actual Budget",
|
||||
"version": "latest",
|
||||
"description": "A super fast and privacy-focused app for managing your finances.",
|
||||
"logo": "actualbudget.png",
|
||||
"links": {
|
||||
"github": "https://github.com/actualbudget/actual",
|
||||
"website": "https://actualbudget.org",
|
||||
"docs": "https://actualbudget.org/docs"
|
||||
},
|
||||
"tags": [
|
||||
"budgeting",
|
||||
"finance",
|
||||
"money"
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# dokploy: allow-host-ports — DNS (53/tcp+udp), DHCP (67/68) and DNS-over-TLS/QUIC (853) are not HTTP protocols; clients must reach them directly on the host, Traefik cannot route them.
|
||||
version: "3.8"
|
||||
services:
|
||||
adguardhome:
|
||||
image: adguard/adguardhome:latest
|
||||
@@ -10,12 +10,11 @@ services:
|
||||
- "68:68/tcp" # DHCP Client
|
||||
- "853:853/tcp" # DNS over TLS, DNS-over-QUIC
|
||||
- "853:853/udp" # DNS over TLS, DNS-over-QUIC
|
||||
expose:
|
||||
- 6060 # HTTP (pprof) — debug endpoint, no need to publish it on the host
|
||||
- "6060:6060/tcp" # HTTP (pprof)
|
||||
volumes:
|
||||
- adguardhome-work:/opt/adguardhome/work
|
||||
- adguardhome-conf:/opt/adguardhome/conf
|
||||
|
||||
volumes:
|
||||
adguardhome-work: {}
|
||||
adguardhome-conf: {}
|
||||
adguardhome-conf: {}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"id": "adguardhome",
|
||||
"name": "AdGuard Home",
|
||||
"version": "latest",
|
||||
"description": "AdGuard Home is a network-wide ad and tracker blocker. Self-host it on your own server with Dokploy, which deploys AdGuard Home as a Docker Compose service in a few clicks.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/AdguardTeam/AdGuardHome",
|
||||
"website": "https://adguard.com",
|
||||
"docs": "https://github.com/AdguardTeam/AdGuardHome/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"privacy",
|
||||
"security",
|
||||
"dns",
|
||||
"ad-blocking"
|
||||
],
|
||||
"seoTitle": "AdGuard Home Docker Compose Setup",
|
||||
"seoDescription": "Network-wide ad and tracker blocker. Deploy AdGuard Home with Docker Compose on Dokploy in minutes – free, open source, self-hosted.",
|
||||
"newConfig": true
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
adminer:
|
||||
image: adminer:4.8.1
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 8080
|
||||
ports:
|
||||
- 8080
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"id": "adminer",
|
||||
"name": "Adminer",
|
||||
"version": "4.8.1",
|
||||
"description": "Adminer is a lightweight, web-based database management tool. Self-host it on your own server with Dokploy, which deploys Adminer as a Docker Compose service in a few clicks.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/vrana/adminer",
|
||||
"website": "https://www.adminer.org/",
|
||||
"docs": "https://www.adminer.org/en/plugins/"
|
||||
},
|
||||
"tags": [
|
||||
"databases",
|
||||
"developer-tools",
|
||||
"mysql",
|
||||
"postgresql"
|
||||
],
|
||||
"seoTitle": "Adminer Docker Compose Setup",
|
||||
"seoDescription": "Lightweight database management tool. Deploy Adminer with Docker Compose on Dokploy in minutes – free, open source, self-hosted.",
|
||||
"newConfig": true
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "adventurelog",
|
||||
"name": "AdventureLog",
|
||||
"version": "latest",
|
||||
"description": "AdventureLog is an open-source activity tracker with maps, journaling, and Strava integration.",
|
||||
"logo": "adventurelog.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/seanmorley15/adventurelog",
|
||||
"website": "https://adventurelog.app/",
|
||||
"docs": "https://adventurelog.app/docs/intro/adventurelog_overview.html"
|
||||
},
|
||||
"tags": [
|
||||
"activity",
|
||||
"maps",
|
||||
"django",
|
||||
"react",
|
||||
"postgres"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
affinepro:
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
image: ghcr.io/toeverything/affine-graphql:stable-780dd83
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- 3010
|
||||
volumes:
|
||||
- affine-storage:/root/.affine/storage
|
||||
@@ -11,7 +12,6 @@ services:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
@@ -19,21 +19,16 @@ services:
|
||||
- MAILER_PASSWORD=${MAILER_PASSWORD}
|
||||
- MAILER_SENDER=${MAILER_SENDER}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
- db
|
||||
- redis
|
||||
|
||||
migration:
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
command: ["sh", "-c", "node ./scripts/self-host-predeploy.js"]
|
||||
image: ghcr.io/toeverything/affine-graphql:stable-780dd83
|
||||
command: node ./scripts/self-host-predeploy.js
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
@@ -44,25 +39,17 @@ services:
|
||||
- affine-storage:/root/.affine/storage
|
||||
- affine-config:/root/.affine/config
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
- db
|
||||
- redis
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg16
|
||||
image: postgres:15-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=affinepro
|
||||
- POSTGRES_INITDB_ARGS=--data-checksums
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "affinepro"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
@@ -70,14 +57,9 @@ services:
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli -a '${REDIS_PASSWORD}' ping | grep PONG"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
affine-storage: {}
|
||||
affine-config: {}
|
||||
postgres-data: {}
|
||||
redis-data: {}
|
||||
redis-data: {}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"id": "affinepro",
|
||||
"name": "Affine Pro",
|
||||
"version": "stable",
|
||||
"description": "AFFiNE is a workspace for notes, docs, and whiteboards, and an alternative to Notion. Self-host it on your own server with Dokploy, which deploys AFFiNE as a Docker Compose service in a few clicks.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/toeverything/Affine",
|
||||
"website": "https://affine.pro/",
|
||||
"docs": "https://docs.affine.pro/"
|
||||
},
|
||||
"tags": [
|
||||
"collaboration",
|
||||
"self-hosted",
|
||||
"productivity",
|
||||
"project-management"
|
||||
],
|
||||
"seoTitle": "Self-Host Affine in minutes",
|
||||
"seoDescription": "Workspace for notes and docs, an alternative to Notion. Self-host Affine on Dokploy in minutes – no manual setup needed."
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="a" viewBox="0 0 3271 1440"><defs><style>.st0{fill:#dedede}</style></defs><path d="M1009.95 1209.53c-125.1-214.98-250.74-430.9-378.92-651.16-126.82 219.73-251.81 436.28-376.62 652.51H60.69c190.42-329.11 570.87-981.75 570.87-981.75s382.11 651.12 572.49 980.41h-194.1Z" class="st0"/><path d="M875.46 1210.46H383.92c32.72-56.91 64.4-111.99 95.81-166.62H781.8c30.62 54.46 61.09 108.66 93.67 166.62zm1622.92-202.23c19.52 29.84 37.9 57.94 58.46 89.39h-63.61c-18.29-24.88-38.17-51.91-57.71-78.49h-72.24v78.17h-51.11V828.44c30.03 0 58.71-.22 87.38.06 23.48.24 47.32-1.26 70.37 2.21 37.67 5.66 66.94 25.71 74.9 64.9 8.22 40.54 1.62 78.35-35.52 104.62-3.14 2.22-6.21 4.54-10.93 7.99h.01Zm-135.01-40.37c34 0 66.71 1.87 99.08-.66 21.47-1.67 30.97-17.92 31.56-39.77.61-22.88-9.01-40.56-30.99-42.73-32.8-3.23-66.15-.84-99.65-.84zM1674.39 829.9v38.37c-52.36 58.82-104.25 117.13-160.14 179.91h158.93v49.49h-227.62v-51.9c46.98-52.11 95.02-105.41 147.93-164.09h-147.28V829.9zm906.29-489.95c64.48 63.91 125.11 123.99 189.35 187.65V373.68h47.4v271.19c-64.19-62.94-125.05-122.59-190.04-186.3v162.44h-46.72V339.94h.01Zm195.02 692.31c13.79-6.85 33.51-15.76 45.41-21.65 41.44 55.26 105.07 42.53 132.33 18s35.44-59.98 25.2-95.47c-13.36-34.33-42.97-61.14-86.36-59.62-43.38 1.52-81.55 32.41-85.2 84.34h-50.32c-.57-50.11 36.27-109.35 94.12-126.75 72.77-21.89 144.89 13.23 172.07 83.77 25.18 65.37-5.74 138.6-72.21 171-60.69 29.59-140.17 6.97-175.1-53.61h.04Zm-811.82-622.07c-11.07 8.4-24.55 17.68-34.13 24.95-55.99-46.33-119.62-30.88-142.33 27.28-13.63 38.17 6.27 88.2 48.2 103.62 36.45 13.4 76.82-.61 87.32-31.48-8.66-2.61-17.29-5.23-28.25-8.54-.93-12.84-1.85-25.67-2.96-40.9h96.82c7.25 56.02-37.39 118.12-94.41 132.17-63.06 15.54-129.35-19.2-152.31-79.83-22.64-59.76 4.81-131.25 61.88-161.16 55.93-29.3 126.75-15.37 160.17 33.88Zm-531.9-55.25c48.2 92.28 93.04 178.13 139.88 267.84h-53.47c-27.89-50.55-56.47-102.34-86.73-157.13-28.93 54.29-56.1 105.29-83.82 157.31h-54.54c45.87-88.68 90.79-175.5 138.65-268.02h.01Zm1642.09 60.65h-89.98v-46.22h226.22v46.24h-86.43v205.91h-49.82V415.59zM1947.89 878.34v65.77l63.09.91s-.03 30.03-.03 50.34c-25.69.89-53.1.45-78.35-5.53-21.38-5.05-36.73-22.63-37.67-46.05-1.5-36.98-.4-75.88-.4-115.24h216.51v49.81h-163.16Zm-52.88 218.57v-68.62c14.11-.65 27.22-1.25 40.97-1.87 3.47 7 3.06 19.81 9.96 20.09 6.88.29 107.67 0 164.04 0v50.41h-214.97Zm342.4-678.53v60.83l58.36.84s-.03 27.78-.03 46.57c-23.85.41-49.12.41-72.49-5.11-19.77-4.68-33.97-20.93-34.85-42.59-1.39-34.2-.37-70.19-.37-106.6h200.29v46.07H2237.4h.01Zm-48.91 202.2v-63.49c13.05-.6 25.18-1.15 37.9-1.73 3.2 6.48 2.83 18.32 9.2 18.59s99.6 0 151.75 0v46.63h-198.87.01Z" class="st0"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,13 +0,0 @@
|
||||
services:
|
||||
agent-zero:
|
||||
image: agent0ai/agent-zero:v1.9
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "80"
|
||||
environment:
|
||||
- AUTH_LOGIN=${AUTH_LOGIN}
|
||||
- AUTH_PASSWORD=${AUTH_PASSWORD}
|
||||
volumes:
|
||||
- agent-zero-data:/a0/usr
|
||||
volumes:
|
||||
agent-zero-data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "agent-zero",
|
||||
"name": "Agent Zero",
|
||||
"version": "v1.9",
|
||||
"description": "Agent Zero is an open-source autonomous AI agent framework with memory and tool use capabilities.",
|
||||
"logo": "agent-zero.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/agent0ai/agent-zero",
|
||||
"website": "https://www.agent-zero.ai",
|
||||
"docs": "https://www.agent-zero.ai/p/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"agent",
|
||||
"automation"
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
auth_login = "${username}"
|
||||
auth_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"AUTH_LOGIN=${auth_login}",
|
||||
"AUTH_PASSWORD=${auth_password}",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "agent-zero"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
@@ -11,11 +11,12 @@ services:
|
||||
- agentdvr-config:/AgentDVR/Media/XML/
|
||||
- agentdvr-media:/AgentDVR/Media/WebServerRoot/Media/
|
||||
- agentdvr-commands:/AgentDVR/Commands/
|
||||
expose:
|
||||
ports:
|
||||
- 8090
|
||||
- 3478/udp
|
||||
- 50000-50100/udp
|
||||
|
||||
volumes:
|
||||
agentdvr-config: {}
|
||||
agentdvr-media: {}
|
||||
agentdvr-commands: {}
|
||||
agentdvr-commands: {}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"id": "agentdvr",
|
||||
"name": "Agent DVR",
|
||||
"version": "latest",
|
||||
"description": "Agent DVR is a comprehensive video surveillance software with motion detection, alerts, and remote access capabilities.",
|
||||
"logo": "agentdvr.png",
|
||||
"links": {
|
||||
"github": "https://github.com/ispysoftware/AgentDVR",
|
||||
"website": "https://www.ispyconnect.com/",
|
||||
"docs": "https://www.ispyconnect.com/userguide-agent-dvr.aspx"
|
||||
},
|
||||
"tags": [
|
||||
"surveillance",
|
||||
"security",
|
||||
"video",
|
||||
"monitoring",
|
||||
"dvr",
|
||||
"camera"
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,6 @@ main_domain = "${domain}"
|
||||
timezone = "America/New_York"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
[[config.domains]]
|
||||
serviceName = "agentdvr"
|
||||
port = 8090
|
||||
@@ -11,3 +10,5 @@ host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
TIMEZONE = "${timezone}"
|
||||
|
||||
[[config.mounts]]
|
||||
@@ -1,29 +0,0 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 95.7 99" style="enable-background:new 0 0 95.7 99;" xml:space="preserve">
|
||||
<title>Airbyte</title>
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#615EFF;}
|
||||
</style>
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
<slices>
|
||||
</slices>
|
||||
<sliceSourceBounds bottomLeftOrigin="true" height="99" width="95.7" x="77.2" y="75">
|
||||
</sliceSourceBounds>
|
||||
</sfw>
|
||||
</metadata>
|
||||
<g>
|
||||
<path class="st0" d="M32.6,12C43.2,0.1,60.8-3.4,75.2,3.5c19.2,9.1,26.1,32.4,15.7,50.3L67.4,94.1c-1.3,2.3-3.5,3.9-6,4.6
|
||||
c-2.5,0.7-5.2,0.3-7.5-1l28.4-48.8c7.6-13,2.5-29.9-11.4-36.5c-10.4-5-23.2-2.5-30.9,6.1c-4.2,4.7-6.6,10.8-6.7,17.1
|
||||
c-0.1,6.3,2.2,12.5,6.3,17.3c0.7,0.9,1.5,1.7,2.4,2.4L25.5,83.8c-0.6,1.1-1.5,2.1-2.5,2.9c-1,0.8-2.2,1.4-3.5,1.7
|
||||
c-1.3,0.3-2.6,0.4-3.8,0.3c-1.3-0.2-2.5-0.6-3.6-1.2l18-31c-2.6-3.7-4.4-7.9-5.5-12.2l-11,19c-1.3,2.3-3.5,3.9-6,4.6S2.3,68,0,66.7
|
||||
l28.5-49.1C29.7,15.7,31.1,13.8,32.6,12z M65.6,27.6c6.9,3.9,9.2,12.7,5.3,19.5L43.5,94.1c-1.3,2.3-3.5,3.9-6,4.6
|
||||
c-2.5,0.7-5.2,0.3-7.5-1L55.4,54c-2-0.4-4-1.3-5.6-2.5c-1.7-1.2-3.1-2.8-4-4.6c-1-1.8-1.6-3.8-1.7-5.9c-0.1-2.1,0.2-4.1,0.9-6.1
|
||||
c0.7-1.9,1.9-3.7,3.4-5.1c1.5-1.4,3.3-2.5,5.2-3.2c2-0.7,4.1-0.9,6.1-0.8C61.8,26,63.8,26.6,65.6,27.6z M55.6,36.5
|
||||
c-0.5,0.4-0.9,0.8-1.2,1.3h0c-0.4,0.8-0.7,1.7-0.6,2.5c0.1,0.9,0.4,1.7,0.9,2.4c0.5,0.7,1.3,1.2,2.1,1.5c0.8,0.3,1.8,0.3,2.6,0.1
|
||||
c0.9-0.2,1.6-0.7,2.2-1.4c0.6-0.7,1-1.5,1.1-2.4c0.1-0.9,0-1.8-0.4-2.6c-0.4-0.8-1-1.5-1.8-1.9c-0.5-0.3-1.1-0.5-1.7-0.6
|
||||
s-1.2,0-1.8,0.1C56.6,35.8,56.1,36.1,55.6,36.5z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,428 +0,0 @@
|
||||
# The following template is not officially supported by the Airbyte team, as
|
||||
# docker compose deployment has been superseded by abctl. It was made based
|
||||
# on the community-maintained example below:
|
||||
#
|
||||
# - https://github.com/nktnet1/airbyte-compose
|
||||
#
|
||||
# NOTE: make sure to change the AIRBYTE_URL and AIRBYTE_API_HOST environment
|
||||
# variables to match that of your public domain. The URL must be publicly
|
||||
# accessible (for example, it cannot be auth-protected by Cloudflare).
|
||||
#
|
||||
# Once you've completed the setup via the web UI, you can log in using the
|
||||
# password defined in the AIRBYTE_ADMIN_PASSWORD environment variable.
|
||||
#
|
||||
# To connect internal databases, ensure that they are on the same network as
|
||||
# the k3s container. This is available in Dokploy's UI starting from v0.30.0.
|
||||
|
||||
name: airbyte
|
||||
|
||||
x-airbyte-environment: &airbyte-environment
|
||||
AIRBYTE_VERSION: ${AIRBYTE_VERSION:?Set AIRBYTE_VERSION in .env}
|
||||
AIRBYTE_URL: ${AIRBYTE_URL:?Set AIRBYTE_URL in .env}
|
||||
AIRBYTE_API_HOST: ${AIRBYTE_API_HOST:?Set AIRBYTE_API_HOST in .env}
|
||||
AIRBYTE_SERVER_HOST: server:8001
|
||||
INTERNAL_API_HOST: http://server:8001
|
||||
MANIFEST_SERVER_API_HOST: http://manifest-server:8080
|
||||
CONTROL_PLANE_TOKEN_ENDPOINT: http://server:8001/api/v1/dataplanes/token
|
||||
WORKLOAD_API_HOST: http://workload-api-server:8007
|
||||
TEMPORAL_HOST: temporal:7233
|
||||
MINIO_ENDPOINT: http://minio:9000
|
||||
DATABASE_HOST: db
|
||||
DATABASE_DB: ${DATABASE_DB:?Set DATABASE_DB in .env}
|
||||
DATABASE_USER: ${DATABASE_USER:?Set DATABASE_USER in .env}
|
||||
DATABASE_PASSWORD: ${DATABASE_PASSWORD:?Set DATABASE_PASSWORD in .env}
|
||||
DATABASE_URL: jdbc:postgresql://db:5432/${DATABASE_DB}
|
||||
CONFIG_DATABASE_REPLICA_URL: jdbc:postgresql://db:5432/${DATABASE_DB}
|
||||
CONFIG_DATABASE_REPLICA_USER: ${DATABASE_USER}
|
||||
CONFIG_DATABASE_REPLICA_PASSWORD: ${DATABASE_PASSWORD}
|
||||
AWS_ACCESS_KEY_ID: ${MINIO_ROOT_USER:?Set MINIO_ROOT_USER in .env}
|
||||
AWS_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:?Set MINIO_ROOT_PASSWORD in .env}
|
||||
AB_INSTANCE_ADMIN_PASSWORD: ${AIRBYTE_ADMIN_PASSWORD:?Set AIRBYTE_ADMIN_PASSWORD in .env}
|
||||
AB_INSTANCE_ADMIN_CLIENT_ID: ${AIRBYTE_ADMIN_CLIENT_ID:?Set AIRBYTE_ADMIN_CLIENT_ID in .env}
|
||||
AB_INSTANCE_ADMIN_CLIENT_SECRET: ${AIRBYTE_ADMIN_CLIENT_SECRET:?Set AIRBYTE_ADMIN_CLIENT_SECRET in .env}
|
||||
AB_JWT_SIGNATURE_SECRET: ${AIRBYTE_JWT_SIGNATURE_SECRET:?Set AIRBYTE_JWT_SIGNATURE_SECRET in .env}
|
||||
CONNECTOR_PROFILER_IMAGE: airbyte/async-profiler:${AIRBYTE_VERSION}
|
||||
CONNECTOR_SIDECAR_IMAGE: airbyte/connector-sidecar:${AIRBYTE_VERSION}
|
||||
CONTAINER_ORCHESTRATOR_IMAGE: airbyte/container-orchestrator:${AIRBYTE_VERSION}
|
||||
WORKLOAD_INIT_IMAGE: airbyte/workload-init-container:${AIRBYTE_VERSION}
|
||||
JAVA_OPTS: -XX:+ExitOnOutOfMemoryError -XX:MaxRAMPercentage=75.0 -XX:FlightRecorderOptions=stackdepth=256
|
||||
|
||||
x-airbyte-service: &airbyte-service
|
||||
env_file:
|
||||
- ../files/config/airbyte.env
|
||||
environment: *airbyte-environment
|
||||
restart: unless-stopped
|
||||
|
||||
services:
|
||||
db:
|
||||
image: airbyte/db:${AIRBYTE_VERSION}
|
||||
environment:
|
||||
POSTGRES_DB: ${DATABASE_DB}
|
||||
POSTGRES_USER: ${DATABASE_USER}
|
||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
healthcheck:
|
||||
test: [CMD-SHELL, 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
minio:
|
||||
# S3-compatible storage (SeaweedFS). The service keeps the "minio" name so
|
||||
# Airbyte's MINIO_ENDPOINT (http://minio:9000) and the k3s ExternalName
|
||||
# service keep working unchanged.
|
||||
image: chrislusf/seaweedfs:${SEAWEEDFS_VERSION}
|
||||
entrypoint: [/bin/sh, -ec]
|
||||
command:
|
||||
- |
|
||||
mkdir -p /etc/seaweedfs
|
||||
printf '{"identities":[{"name":"airbyte","credentials":[{"accessKey":"%s","secretKey":"%s"}],"actions":["Admin","Read","Write","List","Tagging"]}]}' \
|
||||
"$${MINIO_ROOT_USER}" "$${MINIO_ROOT_PASSWORD}" > /etc/seaweedfs/s3.json
|
||||
exec weed server -dir=/storage -ip.bind=0.0.0.0 \
|
||||
-master.volumeSizeLimitMB=1024 -volume.max=0 \
|
||||
-s3 -s3.port=9000 -s3.config=/etc/seaweedfs/s3.json
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- minio-data:/storage
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: [CMD-SHELL, "wget -q -O /dev/null http://127.0.0.1:9000/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 20s
|
||||
|
||||
minio-init:
|
||||
image: chrislusf/seaweedfs:${SEAWEEDFS_VERSION}
|
||||
entrypoint: [/bin/sh, /scripts/init-minio.sh]
|
||||
environment:
|
||||
SEAWEEDFS_MASTER: minio:9333
|
||||
volumes:
|
||||
- ../files/scripts/init-minio.sh:/scripts/init-minio.sh:ro
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
|
||||
temporal:
|
||||
image: temporalio/auto-setup:${TEMPORAL_VERSION}
|
||||
environment:
|
||||
AUTO_SETUP: "true"
|
||||
DB: postgres12
|
||||
DB_PORT: "5432"
|
||||
POSTGRES_SEEDS: db
|
||||
POSTGRES_USER: ${DATABASE_USER}
|
||||
POSTGRES_PWD: ${DATABASE_PASSWORD}
|
||||
POSTGRES_TLS_ENABLED: "false"
|
||||
POSTGRES_TLS_DISABLE_HOST_VERIFICATION: "false"
|
||||
SQL_TLS_ENABLED: "false"
|
||||
SQL_TLS_DISABLE_HOST_VERIFICATION: "false"
|
||||
DYNAMIC_CONFIG_FILE_PATH: config/dynamicconfig/development.yaml
|
||||
PROMETHEUS_ENDPOINT: 0.0.0.0:9090
|
||||
BIND_ON_IP: 0.0.0.0
|
||||
TEMPORAL_BROADCAST_ADDRESS: 0.0.0.0
|
||||
volumes:
|
||||
- ../files/config/temporal-dynamicconfig.yaml:/etc/temporal/config/dynamicconfig/development.yaml:ro
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- exec 3<>/dev/tcp/127.0.0.1/7233
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 30s
|
||||
|
||||
k3s:
|
||||
image: rancher/k3s:${K3S_VERSION}
|
||||
command:
|
||||
- server
|
||||
- --node-name=airbyte-k3s
|
||||
- --disable=traefik
|
||||
- --disable=servicelb
|
||||
- --disable=metrics-server
|
||||
- --disable=coredns
|
||||
- --tls-san=k3s
|
||||
privileged: true
|
||||
environment:
|
||||
K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml
|
||||
K3S_KUBECONFIG_MODE: "0644"
|
||||
tmpfs:
|
||||
- /run
|
||||
- /var/run
|
||||
volumes:
|
||||
- k3s-data:/var/lib/rancher/k3s
|
||||
- k3s-kubeconfig:/output
|
||||
healthcheck:
|
||||
test: [CMD, /bin/kubectl, get, --raw=/readyz]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 60
|
||||
start_period: 20s
|
||||
restart: unless-stopped
|
||||
|
||||
docker-dns:
|
||||
image: rancher/mirrored-coredns-coredns:${COREDNS_VERSION}
|
||||
command: [-conf, /etc/coredns/Corefile]
|
||||
volumes:
|
||||
- ../files/config/docker-dns.Corefile:/etc/coredns/Corefile:ro
|
||||
network_mode: service:k3s
|
||||
depends_on:
|
||||
k3s:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: [CMD, /coredns, -version]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
k3s-bootstrap:
|
||||
image: rancher/k3s:${K3S_VERSION}
|
||||
entrypoint: [/bin/sh, /scripts/bootstrap-k3s.sh]
|
||||
environment:
|
||||
AIRBYTE_ADMIN_PASSWORD: ${AIRBYTE_ADMIN_PASSWORD}
|
||||
AIRBYTE_ADMIN_CLIENT_ID: ${AIRBYTE_ADMIN_CLIENT_ID}
|
||||
AIRBYTE_ADMIN_CLIENT_SECRET: ${AIRBYTE_ADMIN_CLIENT_SECRET}
|
||||
AIRBYTE_JWT_SIGNATURE_SECRET: ${AIRBYTE_JWT_SIGNATURE_SECRET}
|
||||
DATABASE_USER: ${DATABASE_USER}
|
||||
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- k3s-kubeconfig:/kube
|
||||
- ../files/config/k3s-resources.yaml:/config/k3s-resources.yaml:ro
|
||||
- ../files/scripts/bootstrap-k3s.sh:/scripts/bootstrap-k3s.sh:ro
|
||||
depends_on:
|
||||
k3s:
|
||||
condition: service_healthy
|
||||
docker-dns:
|
||||
condition: service_started
|
||||
restart: on-failure
|
||||
|
||||
dataplane-credentials:
|
||||
image: rancher/k3s:${K3S_VERSION}
|
||||
entrypoint: [/bin/sh, /scripts/export-dataplane-credentials.sh]
|
||||
volumes:
|
||||
- k3s-kubeconfig:/kube:ro
|
||||
- dataplane-credentials:/credentials
|
||||
- ../files/scripts/export-dataplane-credentials.sh:/scripts/export-dataplane-credentials.sh:ro
|
||||
depends_on:
|
||||
bootloader:
|
||||
condition: service_completed_successfully
|
||||
restart: on-failure
|
||||
|
||||
bootloader:
|
||||
<<: *airbyte-service
|
||||
image: airbyte/bootloader:${AIRBYTE_VERSION}
|
||||
environment:
|
||||
<<: *airbyte-environment
|
||||
KUBECONFIG: /kube/config.yaml
|
||||
MICRONAUT_ENVIRONMENTS: k8s,control-plane,edition-community
|
||||
JOB_KUBE_NAMESPACE: default
|
||||
DD_SERVICE: airbyte-bootloader
|
||||
volumes:
|
||||
- k3s-kubeconfig:/kube:ro
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
k3s-bootstrap:
|
||||
condition: service_completed_successfully
|
||||
restart: on-failure
|
||||
|
||||
server:
|
||||
<<: *airbyte-service
|
||||
image: airbyte/server:${AIRBYTE_VERSION}
|
||||
environment:
|
||||
<<: *airbyte-environment
|
||||
DD_SERVICE: airbyte-server
|
||||
expose:
|
||||
- 8001
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
exec 3<>/dev/tcp/127.0.0.1/8001
|
||||
printf 'GET /api/v1/health HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
|
||||
read -r status <&3
|
||||
[[ "$$status" == *" 200 "* ]]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 60
|
||||
start_period: 10s
|
||||
depends_on:
|
||||
bootloader:
|
||||
condition: service_completed_successfully
|
||||
minio-init:
|
||||
condition: service_completed_successfully
|
||||
temporal:
|
||||
condition: service_started
|
||||
|
||||
manifest-server:
|
||||
<<: *airbyte-service
|
||||
image: airbyte/manifest-server:${MANIFEST_SERVER_VERSION}
|
||||
environment:
|
||||
<<: *airbyte-environment
|
||||
DD_SERVICE: airbyte-manifest-server
|
||||
AIRBYTE_ENABLE_UNSAFE_CODE: ${AIRBYTE_ENABLE_UNSAFE_CODE:-false}
|
||||
depends_on:
|
||||
bootloader:
|
||||
condition: service_completed_successfully
|
||||
minio-init:
|
||||
condition: service_completed_successfully
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- python
|
||||
- -c
|
||||
- import urllib.request; response = urllib.request.urlopen("http://127.0.0.1:8080/health/", timeout=5); assert response.status == 200
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
workload-api-server:
|
||||
<<: *airbyte-service
|
||||
image: airbyte/workload-api-server:${AIRBYTE_VERSION}
|
||||
environment:
|
||||
<<: *airbyte-environment
|
||||
DD_SERVICE: airbyte-workload-api
|
||||
depends_on:
|
||||
bootloader:
|
||||
condition: service_completed_successfully
|
||||
minio-init:
|
||||
condition: service_completed_successfully
|
||||
temporal:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
exec 3<>/dev/tcp/127.0.0.1/8085
|
||||
printf 'GET /health/liveness HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
|
||||
read -r status <&3
|
||||
[[ "$$status" == *" 200 "* ]]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
worker:
|
||||
<<: *airbyte-service
|
||||
image: airbyte/worker:${AIRBYTE_VERSION}
|
||||
environment:
|
||||
<<: *airbyte-environment
|
||||
DD_SERVICE: airbyte-worker
|
||||
depends_on:
|
||||
bootloader:
|
||||
condition: service_completed_successfully
|
||||
temporal:
|
||||
condition: service_started
|
||||
workload-api-server:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
exec 3<>/dev/tcp/127.0.0.1/9000
|
||||
printf 'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
|
||||
read -r status <&3
|
||||
[[ "$$status" == *" 200 "* ]]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
cron:
|
||||
<<: *airbyte-service
|
||||
image: airbyte/cron:${AIRBYTE_VERSION}
|
||||
environment:
|
||||
<<: *airbyte-environment
|
||||
DD_SERVICE: airbyte-cron
|
||||
depends_on:
|
||||
bootloader:
|
||||
condition: service_completed_successfully
|
||||
temporal:
|
||||
condition: service_started
|
||||
workload-api-server:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
exec 3<>/dev/tcp/127.0.0.1/8085
|
||||
printf 'GET /health HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
|
||||
read -r status <&3
|
||||
[[ "$$status" == *" 200 "* ]]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
workload-launcher:
|
||||
<<: *airbyte-service
|
||||
image: airbyte/workload-launcher:${AIRBYTE_VERSION}
|
||||
entrypoint: [/bin/sh, /scripts/start-workload-launcher.sh]
|
||||
command: [/bin/bash, -c, 'airbyte-app/bin/$${APPLICATION}']
|
||||
environment:
|
||||
<<: *airbyte-environment
|
||||
KUBECONFIG: /kube/config.yaml
|
||||
MICRONAUT_ENVIRONMENTS: k8s,control-plane,edition-community
|
||||
JOB_KUBE_NAMESPACE: default
|
||||
DD_SERVICE: airbyte-workload-launcher
|
||||
AIRBYTE_ENABLE_UNSAFE_CODE: ${AIRBYTE_ENABLE_UNSAFE_CODE:-false}
|
||||
volumes:
|
||||
- k3s-kubeconfig:/kube:ro
|
||||
- dataplane-credentials:/credentials:ro
|
||||
- ../files/scripts/start-workload-launcher.sh:/scripts/start-workload-launcher.sh:ro
|
||||
depends_on:
|
||||
dataplane-credentials:
|
||||
condition: service_completed_successfully
|
||||
k3s-bootstrap:
|
||||
condition: service_completed_successfully
|
||||
server:
|
||||
condition: service_healthy
|
||||
workload-api-server:
|
||||
condition: service_started
|
||||
temporal:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
exec 3<>/dev/tcp/127.0.0.1/8085
|
||||
printf 'GET /health/readiness HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
|
||||
read -r status <&3
|
||||
[[ "$$status" == *" 200 "* ]]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
minio-data:
|
||||
k3s-data:
|
||||
k3s-kubeconfig:
|
||||
dataplane-credentials:
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"id": "airbyte",
|
||||
"name": "Airbyte",
|
||||
"version": "2.2.0",
|
||||
"description": "Open-source data movement for ELT pipelines and AI agents.",
|
||||
"logo": "airbyte.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/airbytehq/airbyte",
|
||||
"website": "https://airbyte.com",
|
||||
"docs": "https://docs.airbyte.com"
|
||||
},
|
||||
"tags": [
|
||||
"analytics",
|
||||
"data",
|
||||
"elt",
|
||||
"pipeline"
|
||||
]
|
||||
}
|
||||
@@ -1,656 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
database_password = "${password:32}"
|
||||
minio_root_password = "${password:32}"
|
||||
airbyte_admin_password = "${password:32}"
|
||||
airbyte_admin_client_id = "${password:32}"
|
||||
airbyte_admin_client_secret = "${password:32}"
|
||||
airbyte_jwt_signature_secret = "${password:32}"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "server"
|
||||
port = 8_001
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
AIRBYTE_URL = "http://${main_domain}"
|
||||
AIRBYTE_API_HOST = "http://${main_domain}/api/public"
|
||||
|
||||
AIRBYTE_VERSION = "2.2.0"
|
||||
MANIFEST_SERVER_VERSION = "7.23.7"
|
||||
TEMPORAL_VERSION = "1.27.2"
|
||||
SEAWEEDFS_VERSION = "4.48"
|
||||
K3S_VERSION = "v1.36.3-k3s1"
|
||||
COREDNS_VERSION = "1.14.7"
|
||||
|
||||
DATABASE_DB = "db-airbyte"
|
||||
DATABASE_USER = "airbyte"
|
||||
DATABASE_PASSWORD = "${database_password}"
|
||||
|
||||
MINIO_ROOT_USER = "minio"
|
||||
MINIO_ROOT_PASSWORD = "${minio_root_password}"
|
||||
|
||||
AIRBYTE_ADMIN_PASSWORD = "${airbyte_admin_password}"
|
||||
AIRBYTE_ADMIN_CLIENT_ID = "${airbyte_admin_client_id}"
|
||||
AIRBYTE_ADMIN_CLIENT_SECRET = "${airbyte_admin_client_secret}"
|
||||
AIRBYTE_JWT_SIGNATURE_SECRET = "${airbyte_jwt_signature_secret}"
|
||||
|
||||
AIRBYTE_ENABLE_UNSAFE_CODE = "false"
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./config/airbyte.env"
|
||||
content = """
|
||||
AB_AUTH_SECRET_CREATION_ENABLED=true
|
||||
AB_KUBERNETES_SECRET_NAME=airbyte-auth-secrets
|
||||
AB_INSTANCE_ADMIN_PASSWORD_SECRET_KEY=instance-admin-password
|
||||
AB_INSTANCE_ADMIN_CLIENT_ID_SECRET_KEY=instance-admin-client-id
|
||||
AB_INSTANCE_ADMIN_CLIENT_SECRET_SECRET_KEY=instance-admin-client-secret
|
||||
AB_JWT_SIGNATURE_SECRET_KEY=jwt-signature-secret
|
||||
DATAPLANE_CLIENT_ID_SECRET_NAME=airbyte-auth-secrets
|
||||
DATAPLANE_CLIENT_ID_SECRET_KEY=dataplane-client-id
|
||||
DATAPLANE_CLIENT_SECRET_SECRET_NAME=airbyte-auth-secrets
|
||||
DATAPLANE_CLIENT_SECRET_SECRET_KEY=dataplane-client-secret
|
||||
IDENTITY_PROVIDER_TYPE=simple
|
||||
AB_AIRBYTE_AUTH_IDENTITY_PROVIDER_VERIFY_ISSUER=false
|
||||
AB_AIRBYTE_AUTH_IDENTITY_PROVIDER_VERIFY_AUDIENCE=false
|
||||
AB_COOKIE_SECURE=false
|
||||
AB_COOKIE_SAME_SITE=Lax
|
||||
|
||||
AIRBYTE_EDITION=community
|
||||
AIRBYTE_CLUSTER_TYPE=hybrid
|
||||
CLUSTER_TYPE=hybrid
|
||||
API_AUTHORIZATION_ENABLED=true
|
||||
LOCAL=false
|
||||
DEPLOYMENT_ENV=docker-compose
|
||||
AIRBYTE_DUMMY_SECRET=dummy
|
||||
|
||||
CONNECTOR_REGISTRY_SEED_PROVIDER=remote
|
||||
ENTERPRISE_SOURCE_STUBS_URL=https://connectors.airbyte.com/files/resources/connector_stubs/v1/connector_stubs.json
|
||||
UPDATE_DEFINITIONS_CRON_ENABLED=true
|
||||
RUN_DECLARATIVE_SOURCES_UPDATER=false
|
||||
CONNECTOR_ROLLOUT_EXPIRATION_SECONDS=604800
|
||||
CONNECTOR_ROLLOUT_WAIT_BETWEEN_ROLLOUTS_SECONDS=10800
|
||||
CONNECTOR_ROLLOUT_WAIT_BETWEEN_SYNC_RESULTS_QUERIES_SECONDS=3600
|
||||
|
||||
DATABASE_PORT=5432
|
||||
RUN_DATABASE_MIGRATION_ON_STARTUP=true
|
||||
CONFIGS_DATABASE_MINIMUM_FLYWAY_MIGRATION_VERSION=0.35.15.001
|
||||
JOBS_DATABASE_MINIMUM_FLYWAY_MIGRATION_VERSION=0.29.15.001
|
||||
|
||||
FEATURE_FLAG_CLIENT=configfile
|
||||
FEATURE_FLAG_PATH=/etc/launchdarkly/flags.yml
|
||||
JOB_KUBE_SERVICEACCOUNT=airbyte-admin
|
||||
JOB_KUBE_NAMESPACE=default
|
||||
JOB_KUBE_LOCAL_VOLUME_ENABLED=false
|
||||
JOB_ERROR_REPORTING_STRATEGY=logging
|
||||
|
||||
LOG_LEVEL=info
|
||||
MICROMETER_METRICS_ENABLED=false
|
||||
MICROMETER_METRICS_STEP=PT1M
|
||||
MICROMETER_METRICS_STATSD_FLAVOR=datadog
|
||||
MICROMETER_METRICS_OTLP_ENABLED=false
|
||||
MICROMETER_METRICS_STATSD_ENABLED=false
|
||||
PUBLISH_METRICS=true
|
||||
STATSD_HOST=127.0.0.1
|
||||
STATSD_PORT=8125
|
||||
OTEL_SDK_DISABLED=true
|
||||
DD_ENABLED=false
|
||||
DD_INTEGRATIONS_ENABLED=false
|
||||
DD_PROFILING_ENABLED=false
|
||||
|
||||
STIGG_ENABLED=false
|
||||
AUDIT_LOGGING_ENABLED=false
|
||||
STORAGE_TYPE=minio
|
||||
STORAGE_BUCKET_ACTIVITY_PAYLOAD=airbyte-bucket
|
||||
STORAGE_BUCKET_AUDIT_LOGGING=airbyte-bucket
|
||||
STORAGE_BUCKET_LOG=airbyte-bucket
|
||||
STORAGE_BUCKET_STATE=airbyte-bucket
|
||||
STORAGE_BUCKET_WORKLOAD_OUTPUT=airbyte-bucket
|
||||
S3_PATH_STYLE_ACCESS=true
|
||||
|
||||
AUTO_SETUP=true
|
||||
DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development.yaml
|
||||
PROMETHEUS_ENDPOINT=0.0.0.0:9090
|
||||
BIND_ON_IP=0.0.0.0
|
||||
TEMPORAL_BROADCAST_ADDRESS=0.0.0.0
|
||||
TEMPORAL_CLOUD_ENABLED=false
|
||||
TEMPORAL_SDK_RPC_TIMEOUT=120s
|
||||
TEMPORAL_SDK_RPC_LONG_POLL_TIMEOUT=140s
|
||||
TEMPORAL_SDK_RPC_QUERY_TIMEOUT=20s
|
||||
TEMPORAL_WORKER_PORTS=9000 9001 9002 9003 9004 9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 9030 9031 9032 9033 9034 9035 9036 9037 9038 9039 9040
|
||||
|
||||
TRACKING_ENABLED=true
|
||||
TRACKING_STRATEGY=segment
|
||||
SEGMENT_WRITE_KEY=7UDdp5K55CyiGgsauOr2pNNujGvmhaeu
|
||||
|
||||
API_URL=/api/v1
|
||||
CONFIG_ROOT=/configs
|
||||
MAX_CHECK_WORKERS=30
|
||||
MAX_NOTIFY_WORKERS=30
|
||||
MAX_SYNC_WORKERS=5
|
||||
SYNC_JOB_MAX_ATTEMPTS=3
|
||||
SYNC_JOB_MAX_TIMEOUT_DAYS=3
|
||||
SYNC_JOB_INIT_RETRY_TIMEOUT_MINUTES=5
|
||||
USE_CUSTOM_NODE_SELECTOR=false
|
||||
USE_STREAM_CAPABLE_STATE=true
|
||||
WORKSPACE_DOCKER_MOUNT=airbyte_workspace
|
||||
WORKSPACE_ROOT=/workspace
|
||||
WORKER_ENVIRONMENT=kubernetes
|
||||
|
||||
WORKLOAD_API_SERVER_ENABLED=true
|
||||
WORKLOAD_LAUNCHER_ENABLED=true
|
||||
WORKLOAD_LAUNCHER_PARALLELISM=10
|
||||
CONTAINER_ORCHESTRATOR_ENABLED=true
|
||||
KUBERNETES_CLIENT_MAX_IDLE_CONNECTIONS=100
|
||||
PUB_SUB_ENABLED=false
|
||||
DATA_CHECK_TASK_QUEUES=CHECK_CONNECTION
|
||||
DATA_DISCOVER_TASK_QUEUES=DISCOVER_SCHEMA
|
||||
DATA_SYNC_TASK_QUEUES=SYNC
|
||||
CONNECTOR_SPECIFIC_RESOURCE_DEFAULTS_ENABLED=true
|
||||
FILE_TRANSFER_EPHEMERAL_STORAGE_LIMIT=5G
|
||||
FILE_TRANSFER_EPHEMERAL_STORAGE_REQUEST=5G
|
||||
|
||||
MICRONAUT_ENVIRONMENTS=k8s,control-plane,edition-community
|
||||
LAUNCHER_MICRONAUT_ENVIRONMENTS=control-plane,oss
|
||||
WORKERS_MICRONAUT_ENVIRONMENTS=control-plane
|
||||
CRON_MICRONAUT_ENVIRONMENTS=control-plane
|
||||
SERVER_MICRONAUT_ENVIRONMENTS=control-plane
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./config/docker-dns.Corefile"
|
||||
content = """
|
||||
.:53 {
|
||||
bind eth0
|
||||
errors
|
||||
cache 30
|
||||
forward . 127.0.0.11
|
||||
}
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./config/k3s-resources.yaml"
|
||||
content = """
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: airbyte-admin
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: airbyte-admin-role
|
||||
namespace: default
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources:
|
||||
- configmaps
|
||||
- endpoints
|
||||
- jobs
|
||||
- pods
|
||||
- pods/log
|
||||
- pods/exec
|
||||
- pods/attach
|
||||
- secrets
|
||||
verbs: [get, list, watch, create, update, patch, delete]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: airbyte-admin-binding
|
||||
namespace: default
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: airbyte-admin-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: airbyte-admin
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: airbyte-db-svc
|
||||
namespace: default
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: db
|
||||
ports:
|
||||
- name: postgresql
|
||||
port: 5432
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: airbyte-minio-svc
|
||||
namespace: default
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: minio
|
||||
ports:
|
||||
- name: api
|
||||
port: 9000
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: airbyte-temporal
|
||||
namespace: default
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: temporal
|
||||
ports:
|
||||
- name: grpc
|
||||
port: 7233
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: airbyte-airbyte-server-svc
|
||||
namespace: default
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: server
|
||||
ports:
|
||||
- name: http
|
||||
port: 8001
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: airbyte-workload-api-server-svc
|
||||
namespace: default
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: workload-api-server
|
||||
ports:
|
||||
- name: http
|
||||
port: 8007
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: airbyte-airbyte-manifest-server-svc
|
||||
namespace: default
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: manifest-server
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: coredns
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: system:coredns
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: [endpoints, services, pods, namespaces]
|
||||
verbs: [list, watch]
|
||||
- apiGroups: [discovery.k8s.io]
|
||||
resources: [endpointslices]
|
||||
verbs: [list, watch]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: system:coredns
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:coredns
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: coredns
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns
|
||||
namespace: kube-system
|
||||
data:
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health
|
||||
ready
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
forward . 127.0.0.1:1053
|
||||
}
|
||||
.:1053 {
|
||||
bind 127.0.0.1
|
||||
errors
|
||||
rewrite stop name regex ^(.+?)(\\.[^.]+\\.svc\\.cluster\\.local|\\.svc\\.cluster\\.local|\\.cluster\\.local)\\.$ {1} answer auto
|
||||
forward . {$AIRBYTE_K3S_IP}
|
||||
}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: coredns
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: kube-dns
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kube-dns
|
||||
spec:
|
||||
priorityClassName: system-cluster-critical
|
||||
serviceAccountName: coredns
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/master
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: coredns
|
||||
image: rancher/mirrored-coredns-coredns:1.14.7
|
||||
imagePullPolicy: IfNotPresent
|
||||
args: [-conf, /etc/coredns/Corefile]
|
||||
env:
|
||||
- name: AIRBYTE_K3S_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.hostIP
|
||||
resources:
|
||||
limits:
|
||||
memory: 170Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 70Mi
|
||||
ports:
|
||||
- name: dns
|
||||
containerPort: 53
|
||||
protocol: UDP
|
||||
- name: dns-tcp
|
||||
containerPort: 53
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/coredns
|
||||
readOnly: true
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
add: [NET_BIND_SERVICE]
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: true
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8181
|
||||
periodSeconds: 2
|
||||
dnsPolicy: Default
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: coredns
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: kube-dns
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: kube-dns
|
||||
kubernetes.io/cluster-service: "true"
|
||||
kubernetes.io/name: CoreDNS
|
||||
spec:
|
||||
selector:
|
||||
k8s-app: kube-dns
|
||||
clusterIP: 10.43.0.10
|
||||
ports:
|
||||
- name: dns
|
||||
port: 53
|
||||
protocol: UDP
|
||||
- name: dns-tcp
|
||||
port: 53
|
||||
protocol: TCP
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./config/temporal-dynamicconfig.yaml"
|
||||
content = """
|
||||
frontend.enableClientVersionCheck:
|
||||
- value: true
|
||||
constraints: {}
|
||||
history.persistenceMaxQPS:
|
||||
- value: 3000
|
||||
constraints: {}
|
||||
frontend.persistenceMaxQPS:
|
||||
- value: 3000
|
||||
constraints: {}
|
||||
frontend.historyMgrNumConns:
|
||||
- value: 30
|
||||
constraints: {}
|
||||
frontend.throttledLogRPS:
|
||||
- value: 20
|
||||
constraints: {}
|
||||
frontend.enableUpdateWorkflowExecution:
|
||||
- value: true
|
||||
frontend.enableUpdateWorkflowExecutionAsyncAccepted:
|
||||
- value: true
|
||||
history.historyMgrNumConns:
|
||||
- value: 50
|
||||
constraints: {}
|
||||
system.advancedVisibilityWritingMode:
|
||||
- value: "off"
|
||||
constraints: {}
|
||||
history.defaultActivityRetryPolicy:
|
||||
- value:
|
||||
InitialIntervalInSeconds: 1
|
||||
MaximumIntervalCoefficient: 100.0
|
||||
BackoffCoefficient: 2.0
|
||||
MaximumAttempts: 0
|
||||
history.defaultWorkflowRetryPolicy:
|
||||
- value:
|
||||
InitialIntervalInSeconds: 1
|
||||
MaximumIntervalCoefficient: 100.0
|
||||
BackoffCoefficient: 2.0
|
||||
MaximumAttempts: 0
|
||||
limit.blobSize.error:
|
||||
- value: 15728640
|
||||
constraints: {}
|
||||
limit.blobSize.warn:
|
||||
- value: 10485760
|
||||
constraints: {}
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./scripts/bootstrap-k3s.sh"
|
||||
content = """
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
source_config=/kube/kubeconfig.yaml
|
||||
client_config=/kube/config.yaml
|
||||
|
||||
until [ -s "$source_config" ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
sed 's|server: https://127.0.0.1:6443|server: https://k3s:6443|' \
|
||||
"$source_config" > "$client_config"
|
||||
chmod 0644 "$client_config"
|
||||
|
||||
/bin/kubectl --kubeconfig "$client_config" \
|
||||
config set-context --current --namespace=default >/dev/null
|
||||
|
||||
until /bin/kubectl --kubeconfig "$client_config" get --raw=/readyz >/dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
/bin/kubectl --kubeconfig "$client_config" apply -f /config/k3s-resources.yaml
|
||||
|
||||
/bin/kubectl --kubeconfig "$client_config" \
|
||||
--namespace kube-system rollout status deployment/coredns --timeout=180s
|
||||
|
||||
/bin/kubectl --kubeconfig "$client_config" create secret generic airbyte-auth-secrets \
|
||||
--namespace default \
|
||||
--from-literal=instance-admin-password="$AIRBYTE_ADMIN_PASSWORD" \
|
||||
--from-literal=instance-admin-client-id="$AIRBYTE_ADMIN_CLIENT_ID" \
|
||||
--from-literal=instance-admin-client-secret="$AIRBYTE_ADMIN_CLIENT_SECRET" \
|
||||
--from-literal=jwt-signature-secret="$AIRBYTE_JWT_SIGNATURE_SECRET" \
|
||||
--from-literal=dataplane-client-id= \
|
||||
--from-literal=dataplane-client-secret= \
|
||||
--dry-run=client -o yaml | \
|
||||
/bin/kubectl --kubeconfig "$client_config" apply -f -
|
||||
|
||||
/bin/kubectl --kubeconfig "$client_config" create secret generic airbyte-airbyte-secrets \
|
||||
--namespace default \
|
||||
--from-literal=AIRBYTE_DUMMY_SECRET=dummy \
|
||||
--from-literal=AB_INSTANCE_ADMIN_PASSWORD="$AIRBYTE_ADMIN_PASSWORD" \
|
||||
--from-literal=AB_INSTANCE_ADMIN_CLIENT_ID="$AIRBYTE_ADMIN_CLIENT_ID" \
|
||||
--from-literal=AB_INSTANCE_ADMIN_CLIENT_SECRET="$AIRBYTE_ADMIN_CLIENT_SECRET" \
|
||||
--from-literal=AB_JWT_SIGNATURE_SECRET="$AIRBYTE_JWT_SIGNATURE_SECRET" \
|
||||
--from-literal=DATABASE_USER="$DATABASE_USER" \
|
||||
--from-literal=DATABASE_PASSWORD="$DATABASE_PASSWORD" \
|
||||
--from-literal=CONFIG_DATABASE_REPLICA_USER="$DATABASE_USER" \
|
||||
--from-literal=CONFIG_DATABASE_REPLICA_PASSWORD="$DATABASE_PASSWORD" \
|
||||
--from-literal=AWS_ACCESS_KEY_ID="$MINIO_ROOT_USER" \
|
||||
--from-literal=AWS_SECRET_ACCESS_KEY="$MINIO_ROOT_PASSWORD" \
|
||||
--from-literal=DATAPLANE_CLIENT_ID= \
|
||||
--from-literal=DATAPLANE_CLIENT_SECRET= \
|
||||
--dry-run=client -o yaml | \
|
||||
/bin/kubectl --kubeconfig "$client_config" apply -f -
|
||||
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./scripts/export-dataplane-credentials.sh"
|
||||
content = """
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
kubeconfig=/kube/config.yaml
|
||||
namespace=default
|
||||
secret_name=airbyte-auth-secrets
|
||||
credentials_dir=/credentials
|
||||
client_id_file="$credentials_dir/dataplane-client-id"
|
||||
client_secret_file="$credentials_dir/dataplane-client-secret"
|
||||
client_id_tmp="$credentials_dir/.dataplane-client-id.$$"
|
||||
client_secret_tmp="$credentials_dir/.dataplane-client-secret.$$"
|
||||
|
||||
umask 022
|
||||
mkdir -p "$credentials_dir"
|
||||
trap 'rm -f "$client_id_tmp" "$client_secret_tmp"' 0 1 2 15
|
||||
|
||||
client_id_b64=$(
|
||||
/bin/kubectl --kubeconfig "$kubeconfig" \
|
||||
--namespace "$namespace" \
|
||||
get secret "$secret_name" \
|
||||
-o 'jsonpath={.data.dataplane-client-id}'
|
||||
)
|
||||
client_secret_b64=$(
|
||||
/bin/kubectl --kubeconfig "$kubeconfig" \
|
||||
--namespace "$namespace" \
|
||||
get secret "$secret_name" \
|
||||
-o 'jsonpath={.data.dataplane-client-secret}'
|
||||
)
|
||||
|
||||
[ -n "$client_id_b64" ] || {
|
||||
echo "The bootloader did not create a data-plane client ID." >&2
|
||||
exit 1
|
||||
}
|
||||
[ -n "$client_secret_b64" ] || {
|
||||
echo "The bootloader did not create a data-plane client secret." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
printf '%s' "$client_id_b64" | base64 -d > "$client_id_tmp"
|
||||
printf '%s' "$client_secret_b64" | base64 -d > "$client_secret_tmp"
|
||||
|
||||
client_id=$(cat "$client_id_tmp")
|
||||
if ! printf '%s' "$client_id" | grep -Eq \
|
||||
'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'; then
|
||||
echo "The bootloader returned an invalid data-plane client ID." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv -f "$client_id_tmp" "$client_id_file"
|
||||
mv -f "$client_secret_tmp" "$client_secret_file"
|
||||
trap - 0 1 2 15
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./scripts/init-minio.sh"
|
||||
content = """
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
for bucket in airbyte-bucket state-storage airbyte-dev-logs; do
|
||||
until echo "s3.bucket.list" | weed shell -master="$SEAWEEDFS_MASTER" 2>/dev/null | grep -q "^[[:space:]]*$bucket[[:space:]]"; do
|
||||
echo "s3.bucket.create -name $bucket" | weed shell -master="$SEAWEEDFS_MASTER" >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
done
|
||||
echo "bucket $bucket ready"
|
||||
done
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./scripts/start-workload-launcher.sh"
|
||||
content = """
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
client_id_file=/credentials/dataplane-client-id
|
||||
client_secret_file=/credentials/dataplane-client-secret
|
||||
|
||||
[ -s "$client_id_file" ] || {
|
||||
echo "Missing generated data-plane client ID." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -s "$client_secret_file" ] || {
|
||||
echo "Missing generated data-plane client secret." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
DATAPLANE_CLIENT_ID=$(cat "$client_id_file")
|
||||
DATAPLANE_CLIENT_SECRET=$(cat "$client_secret_file")
|
||||
export DATAPLANE_CLIENT_ID DATAPLANE_CLIENT_SECRET
|
||||
|
||||
exec /entrypoint.sh "$@"
|
||||
"""
|
||||
@@ -1,3 +1,5 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
akaunting:
|
||||
image: docker.io/akaunting/akaunting:latest
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "akaunting",
|
||||
"name": "Akaunting",
|
||||
"version": "latest",
|
||||
"description": "Akaunting is a self-hosted, open-source accounting app for small businesses.",
|
||||
"logo": "image.png",
|
||||
"links": {
|
||||
"github": "https://github.com/akaunting/akaunting",
|
||||
"website": "https://akaunting.com",
|
||||
"docs": "https://akaunting.com/docs"
|
||||
},
|
||||
"tags": [
|
||||
"finance",
|
||||
"accounting",
|
||||
"php",
|
||||
"mariadb",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -24,7 +24,6 @@ port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
env = [
|
||||
# App
|
||||
"APP_URL=${main_domain}",
|
||||
@@ -51,4 +50,5 @@ env = [
|
||||
"MYSQL_RANDOM_ROOT_PASSWORD=yes"
|
||||
]
|
||||
|
||||
# No custom mounts needed; volumes are already defined in compose.
|
||||
# No custom mounts needed; volumes are already defined in compose.
|
||||
[[config.mounts]]
|
||||
|
||||
|
Before Width: | Height: | Size: 55 KiB |
@@ -1,30 +0,0 @@
|
||||
services:
|
||||
alarik:
|
||||
image: ghcr.io/achtungsoftware/alarik:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- alarik-storage:/app/Storage
|
||||
environment:
|
||||
- API_BASE_URL=${API_BASE_URL}
|
||||
- CONSOLE_BASE_URL=${CONSOLE_BASE_URL}
|
||||
- ADMIN_USERNAME=${ADMIN_USERNAME}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
- JWT=${JWT}
|
||||
- ALLOW_ACCOUNT_CREATION=${ALLOW_ACCOUNT_CREATION}
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
console:
|
||||
image: ghcr.io/achtungsoftware/alarik-console:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- NUXT_PUBLIC_API_BASE_URL=${API_BASE_URL}
|
||||
- NUXT_PUBLIC_CONSOLE_BASE_URL=${CONSOLE_BASE_URL}
|
||||
- NUXT_PUBLIC_ALLOW_ACCOUNT_CREATION=${ALLOW_ACCOUNT_CREATION}
|
||||
expose:
|
||||
- 3000
|
||||
depends_on:
|
||||
- alarik
|
||||
|
||||
volumes:
|
||||
alarik-storage:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "alarik",
|
||||
"name": "Alarik",
|
||||
"description": "Alarik is a high-performance, S3-compatible object storage.",
|
||||
"logo": "alarik.png",
|
||||
"version": "latest",
|
||||
"links": {
|
||||
"github": "https://github.com/achtungsoftware/alarik",
|
||||
"website": "https://alarik.io/",
|
||||
"docs": "https://alarik.io/docs"
|
||||
},
|
||||
"tags": [
|
||||
"storage",
|
||||
"s3",
|
||||
"object-storage"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
[variables]
|
||||
console_domain = "${domain}"
|
||||
api_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
# S3-compatible API - point AWS CLI / SDKs / rclone / backup tools at this domain
|
||||
[[config.domains]]
|
||||
serviceName = "alarik"
|
||||
port = 8_080
|
||||
host = "${api_domain}"
|
||||
|
||||
# Web management console - log in with the generated admin credentials below
|
||||
[[config.domains]]
|
||||
serviceName = "console"
|
||||
port = 3_000
|
||||
host = "${console_domain}"
|
||||
|
||||
[config.env]
|
||||
API_BASE_URL = "http://${api_domain}"
|
||||
CONSOLE_BASE_URL = "http://${console_domain}"
|
||||
ADMIN_USERNAME = "alarik"
|
||||
ADMIN_PASSWORD = "${password:16}"
|
||||
JWT = "${base64:64}"
|
||||
ALLOW_ACCOUNT_CREATION = "false"
|
||||
@@ -1,6 +1,7 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
alist:
|
||||
image: xhofe/alist:v3.55.0
|
||||
image: xhofe/alist:v3.41.0
|
||||
volumes:
|
||||
- alist-data:/opt/alist/data
|
||||
environment:
|
||||
@@ -10,4 +11,4 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
alist-data:
|
||||
alist-data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "alist",
|
||||
"name": "AList",
|
||||
"version": "v3.55.0",
|
||||
"description": "🗂ï¸A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs.",
|
||||
"logo": "alist.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/AlistGo/alist",
|
||||
"website": "https://alistgo.com/",
|
||||
"docs": "https://alistgo.com/guide/install/docker.html"
|
||||
},
|
||||
"tags": [
|
||||
"file",
|
||||
"webdav",
|
||||
"storage"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
alltube:
|
||||
image: dnomd343/alltube:latest
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- 80
|
||||
environment:
|
||||
- TITLE=${TITLE}
|
||||
- CONVERT=${CONVERT}
|
||||
- STREAM=${STREAM}
|
||||
- REMUX=${REMUX}
|
||||
- REMUX=${REMUX}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "alltube",
|
||||
"name": "AllTube",
|
||||
"version": "latest",
|
||||
"description": "AllTube Download is an application designed to facilitate the downloading of videos from YouTube and other video sites. It provides an HTML GUI for youtube-dl with video conversion capabilities and JSON API support.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/Rudloff/alltube",
|
||||
"website": "https://github.com/Rudloff/alltube",
|
||||
"docs": "https://github.com/Rudloff/alltube/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"media",
|
||||
"video",
|
||||
"downloader"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
ampache:
|
||||
image: ampache/ampache:latest
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- 80
|
||||
volumes:
|
||||
- config:/var/www/config
|
||||
@@ -13,4 +14,4 @@ services:
|
||||
volumes:
|
||||
config: {}
|
||||
log: {}
|
||||
mysql: {}
|
||||
mysql: {}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "ampache",
|
||||
"name": "Ampache",
|
||||
"version": "latest",
|
||||
"description": "Ampache is a web-based audio/video streaming application and file manager allowing you to access your music & videos from anywhere, using almost any internet enabled device.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/ampache/ampache",
|
||||
"website": "http://ampache.org/",
|
||||
"docs": "https://github.com/ampache/ampache/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"media",
|
||||
"music",
|
||||
"streaming"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
anonupload:
|
||||
image: ghcr.io/supernova3339/anonfiles:1
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- 80
|
||||
environment:
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL}
|
||||
@@ -12,4 +13,4 @@ services:
|
||||
- uploads:/var/www/html/uploads
|
||||
|
||||
volumes:
|
||||
uploads: {}
|
||||
uploads: {}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "anonupload",
|
||||
"name": "AnonUpload",
|
||||
"version": "1",
|
||||
"description": "AnonUpload is a secure, anonymous file sharing application that does not require a database. It is built with privacy as a priority, ensuring that the direct filename used is not displayed.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/supernova3339/anonupload",
|
||||
"docs": "https://github.com/Supernova3339/anonupload/blob/main/env.md",
|
||||
"website": "https://anonupload.com/"
|
||||
},
|
||||
"tags": [
|
||||
"file-sharing",
|
||||
"privacy"
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
anse-demo:
|
||||
image: ddiu8081/anse:latest
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"id": "anse",
|
||||
"name": "Anse",
|
||||
"version": "latest",
|
||||
"description": "Anse is an open-source alternative to ChatGPT web UI, supporting OpenAI-compatible APIs.",
|
||||
"logo": "image.png",
|
||||
"links": {
|
||||
"github": "https://github.com/ddiu8081/anse",
|
||||
"website": "https://anse.app/",
|
||||
"docs": "https://github.com/ddiu8081/anse#readme"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"chatbot",
|
||||
"openai",
|
||||
"ui"
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
answer:
|
||||
image: apache/answer:1.4.1
|
||||
expose:
|
||||
ports:
|
||||
- '80'
|
||||
restart: on-failure
|
||||
volumes:
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "answer",
|
||||
"name": "Answer",
|
||||
"version": "v1.4.1",
|
||||
"description": "Answer is an open-source Q&A platform for building a self-hosted question-and-answer service.",
|
||||
"logo": "answer.png",
|
||||
"links": {
|
||||
"github": "https://github.com/apache/answer",
|
||||
"website": "https://answer.apache.org/",
|
||||
"docs": "https://answer.apache.org/docs"
|
||||
},
|
||||
"tags": [
|
||||
"q-and-a",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
anubis:
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- "8923" # Anubis default port
|
||||
environment:
|
||||
# Required: Point to your frontend service locally
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "anubis",
|
||||
"name": "Anubis",
|
||||
"version": "latest",
|
||||
"description": "Anubis is a bot protector, It will block bots from accessing your website.",
|
||||
"logo": "anubis.webp",
|
||||
"links": {
|
||||
"github": "https://github.com/TecharoHQ/anubis",
|
||||
"website": "https://anubis.techaro.lol",
|
||||
"docs": "https://anubis.techaro.lol/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"self-hosted",
|
||||
"bot-protection"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
anythingllm:
|
||||
image: mintplexlabs/anythingllm:latest
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- 3001
|
||||
environment:
|
||||
- STORAGE_DIR=/app/server/storage
|
||||
@@ -12,4 +13,4 @@ services:
|
||||
- SYS_ADMIN
|
||||
|
||||
volumes:
|
||||
storage: {}
|
||||
storage: {}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "anythingllm",
|
||||
"name": "AnythingLLM",
|
||||
"version": "latest",
|
||||
"description": "AnythingLLM is a private AI chat app for working with your own documents. Self-host it on your own server with Dokploy, which deploys AnythingLLM as a Docker Compose service in a few clicks.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/Mintplex-Labs/anything-llm",
|
||||
"website": "https://anythingllm.com/",
|
||||
"docs": "https://docs.anythingllm.com/"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"llm",
|
||||
"chatbot"
|
||||
],
|
||||
"seoTitle": "Self-Host AnythingLLM in minutes",
|
||||
"seoDescription": "Private AI chatbot for your documents. Self-host AnythingLLM on Dokploy in minutes – no manual setup needed."
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# dokploy: allow-host-ports — Anytype clients speak the any-sync protocol (TCP 33010, UDP/QUIC 33020) directly against the server; it is not HTTP, so Traefik cannot route it.
|
||||
# Example: Any-Sync-Bundle with embedded MongoDB and Redis (all-in-one image)
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f compose.aio.yml up -d
|
||||
#
|
||||
# The bundle image already contains MongoDB and Redis. Only the bundle service is required
|
||||
|
||||
services:
|
||||
any-sync-bundle:
|
||||
image: ghcr.io/grishy/any-sync-bundle:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "33010:33010"
|
||||
- "33020:33020/udp"
|
||||
volumes:
|
||||
- ../files/anytype-data:/data
|
||||
environment:
|
||||
# Advertise addresses clients should use. Replace with your server hostname/IP.
|
||||
ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS: "192.168.100.9"
|
||||
|
Before Width: | Height: | Size: 50 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "anytype",
|
||||
"name": "Anytype",
|
||||
"version": "latest",
|
||||
"description": "Anytype is a private, offline-first app for notes and personal knowledge. Self-host it on your own server with Dokploy, which deploys Anytype as a Docker Compose service in a few clicks.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/grishy/any-sync-bundle",
|
||||
"docs": "https://doc.anytype.io/anytype-docs",
|
||||
"website": "https://anytype.io/"
|
||||
},
|
||||
"tags": [
|
||||
"note-taking",
|
||||
"local-first",
|
||||
"peer-to-peer"
|
||||
],
|
||||
"seoTitle": "Self-Host Anytype with Dokploy",
|
||||
"seoDescription": "Private, offline-first personal knowledge base. Self-host Anytype on Dokploy – view your client config afterward with a quick command inside the container."
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
[config]
|
||||
domains = []
|
||||
mounts = []
|
||||
env = []
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"id": "appflowy",
|
||||
"name": "App Flowy",
|
||||
"version": "0.9.3",
|
||||
"description": "AppFlowy is a workspace app for notes, docs, and projects, and an open-source alternative to Notion. Self-host it on your own server with Dokploy, which deploys AppFlowy as a Docker Compose service in a few clicks.",
|
||||
"links": {
|
||||
"github": "https://github.com/AppFlowy-IO/AppFlowy",
|
||||
"website": "https://appflowy.io/",
|
||||
"docs": "https://docs.appflowy.io/docs"
|
||||
},
|
||||
"logo": "appflowy.png",
|
||||
"tags": [
|
||||
"productivity",
|
||||
"self-hosted",
|
||||
"notes",
|
||||
"knowledge-base",
|
||||
"notion-alternative"
|
||||
],
|
||||
"seoTitle": "Self-Host AppFlowy in minutes",
|
||||
"seoDescription": "Workspace app, an alternative to Notion. Self-host AppFlowy on Dokploy in minutes – no manual setup needed."
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
apprise-api:
|
||||
image: linuxserver/apprise-api:latest
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- 8000
|
||||
environment:
|
||||
- PUID=1000
|
||||
@@ -12,4 +13,4 @@ services:
|
||||
- config:/config
|
||||
|
||||
volumes:
|
||||
config: {}
|
||||
config: {}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "apprise-api",
|
||||
"name": "Apprise API",
|
||||
"version": "latest",
|
||||
"description": "Apprise API provides a simple interface for sending notifications to almost all of the most popular notification services available to us today.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/caronc/apprise-api",
|
||||
"website": "https://github.com/caronc/apprise-api",
|
||||
"docs": "https://github.com/caronc/apprise-api/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"notifications",
|
||||
"api"
|
||||
]
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
appsmith:
|
||||
image: appsmith/appsmith-ee:v1.94
|
||||
image: index.docker.io/appsmith/appsmith-ee:v1.29
|
||||
volumes:
|
||||
- appsmith-data:/appsmith-stacks
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
appsmith-data:
|
||||
- ../files/stacks:/appsmith-stacks
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"id": "appsmith",
|
||||
"name": "Appsmith",
|
||||
"version": "v1.94",
|
||||
"description": "Appsmith is a free and open source platform for building internal tools and applications.",
|
||||
"logo": "appsmith.png",
|
||||
"links": {
|
||||
"github": "https://github.com/appsmithorg/appsmith",
|
||||
"website": "https://appsmith.com/",
|
||||
"docs": "https://docs.appsmith.com/"
|
||||
},
|
||||
"tags": [
|
||||
"cms"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"id": "appwrite",
|
||||
"name": "Appwrite",
|
||||
"version": "2.7.0",
|
||||
"description": "Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend apps, packaged as a set of Docker microservices. It includes both a backend server and a fully integrated hosting solution for deploying static and server-side rendered frontends. Appwrite abstracts the complexity and repetitiveness required to build modern apps from scratch and allows you to build secure, full-stack applications faster.\nUsing Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and more services.",
|
||||
"links": {
|
||||
"github": "https://github.com/appwrite/appwrite",
|
||||
"website": "https://appwrite.io/",
|
||||
"docs": "https://appwrite.io/docs"
|
||||
},
|
||||
"logo": "appwrite.svg",
|
||||
"tags": [
|
||||
"database",
|
||||
"firebase",
|
||||
"postgresql",
|
||||
"mongodb",
|
||||
"hosting",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -1,139 +1,51 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
functions_domain = "functions.${main_domain}"
|
||||
sites_domain = "sites.${main_domain}"
|
||||
mqtt_domain = "mqtt.${main_domain}"
|
||||
openssl_key = "${password:32}"
|
||||
notifications_tracking_secret = "${password:32}"
|
||||
db_pw = "${password:32}"
|
||||
db_root_pw = "${password:32}"
|
||||
db_user_pw = "${password:32}"
|
||||
usage_pw = "${password:32}"
|
||||
executor_secret = "${password:32}"
|
||||
jobs_secret = "${password:32}"
|
||||
geo_secret = "${password:32}"
|
||||
# Optional AI code review of GitHub pull requests. Uncomment together with the
|
||||
# _APP_REVIEWS_* variables and the appwrite-hansi service in docker-compose.yml.
|
||||
# reviews_secret = "${password:32}"
|
||||
console_domain = "console.${main_domain}"
|
||||
|
||||
[config]
|
||||
# Build jobs and runtimes join <appName>_default, which isolated deployments never create
|
||||
# (every service sits on the external <appName> network instead).
|
||||
isolated = false
|
||||
env = [
|
||||
"_APP_ENV=production",
|
||||
"_APP_VERSION=2.7.0",
|
||||
"_APP_MEMORY_LIMIT=512M",
|
||||
"_APP_EDITION=self-hosted",
|
||||
"_APP_LOCALE=en",
|
||||
"_APP_WORKER_PER_CORE=6",
|
||||
"_APP_OPTIONS_ABUSE=enabled",
|
||||
"_APP_OPTIONS_ABUSE_INCREASED_LIMIT_PROJECTS=",
|
||||
"_APP_OPTIONS_FORCE_HTTPS=disabled",
|
||||
"_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled",
|
||||
"_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled",
|
||||
"_APP_OPTIONS_ROUTER_PROTECTION=disabled",
|
||||
"_APP_ROUTER_AUTO_CERTIFICATES=enabled",
|
||||
"_APP_LOCKING_ENABLED=enabled",
|
||||
"_APP_OPENSSL_KEY_V1=${openssl_key}",
|
||||
"_APP_OPENSSL_KEY_V2=",
|
||||
"_APP_OPENSSL_KEY_CURRENT=1",
|
||||
"_APP_OPENSSL_KEY_OLDEST=1",
|
||||
"_APP_NOTIFICATIONS_TRACKING_SECRET=${notifications_tracking_secret}",
|
||||
"_APP_NOTIFICATIONS_PUSH_APNS=",
|
||||
"_APP_NOTIFICATIONS_PUSH_APNS_KEY=",
|
||||
"_APP_OPENSSL_KEY_V1=your-secret-key",
|
||||
"_APP_DOMAIN=${main_domain}",
|
||||
"_APP_CONSOLE_DOMAIN=${console_domain}",
|
||||
"_APP_CONSOLE_URL=https://${console_domain}",
|
||||
"_APP_CONSOLE_HOSTNAMES=${console_domain}",
|
||||
"_APP_CONSOLE_TRUSTED_PROJECTS=",
|
||||
"_APP_CUSTOM_DOMAIN_DENY_LIST=example.com,test.com,app.example.com",
|
||||
"_APP_DOMAIN_FUNCTIONS=${functions_domain}",
|
||||
"_APP_DOMAIN_FUNCTIONS_FALLBACK=",
|
||||
"_APP_DOMAIN_SITES=${sites_domain}",
|
||||
"_APP_DOMAIN_TARGET_CNAME=localhost",
|
||||
"_APP_DOMAIN_TARGET_AAAA=::1",
|
||||
"_APP_DOMAIN_TARGET_A=127.0.0.1",
|
||||
"_APP_DOMAIN_TARGET_CAA=",
|
||||
"_APP_DNS=8.8.8.8",
|
||||
"_APP_DNS_EXTERNAL=",
|
||||
"_APP_ALLOWED_INTERNAL_ADDRESSES=",
|
||||
"_APP_TRUSTED_HEADERS=x-forwarded-for",
|
||||
"_APP_DOMAIN_FUNCTIONS=${main_domain}",
|
||||
"_APP_DOMAIN_TARGET=${main_domain}",
|
||||
"_APP_CONSOLE_WHITELIST_ROOT=enabled",
|
||||
"_APP_CONSOLE_WHITELIST_EMAILS=",
|
||||
"_APP_CONSOLE_WHITELIST_IPS=",
|
||||
"_APP_CONSOLE_SCHEMA=appwriteio",
|
||||
"_APP_CONSOLE_SESSION_ALERTS=disabled",
|
||||
"_APP_CONSOLE_COUNTRIES_DENYLIST=",
|
||||
"_APP_CONSOLE_GITHUB_APP_ID=",
|
||||
"_APP_CONSOLE_GITHUB_SECRET=",
|
||||
"_APP_CONSOLE_GITLAB_APP_ID=",
|
||||
"_APP_CONSOLE_GITLAB_SECRET=",
|
||||
"_APP_CONSOLE_GITLAB_ENDPOINT=",
|
||||
"_APP_CONSOLE_BITBUCKET_APP_ID=",
|
||||
"_APP_CONSOLE_BITBUCKET_SECRET=",
|
||||
"_APP_CONSOLE_GOOGLE_APP_ID=",
|
||||
"_APP_CONSOLE_GOOGLE_SECRET=",
|
||||
"_APP_CONSOLE_HOSTNAMES=",
|
||||
"_APP_SYSTEM_EMAIL_NAME=Appwrite",
|
||||
"_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io",
|
||||
"_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io",
|
||||
"_APP_SYSTEM_RESPONSE_FORMAT=",
|
||||
"_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=certs@appwrite.io",
|
||||
"_APP_EMAIL_SECURITY=",
|
||||
"_APP_EMAIL_CERTIFICATES=",
|
||||
"_APP_COMPRESSION_ENABLED=enabled",
|
||||
"_APP_COMPRESSION_MIN_SIZE_BYTES=1024",
|
||||
"_APP_USAGE_STATS=enabled",
|
||||
"_APP_LOGGING_PROVIDER=",
|
||||
"_APP_LOGGING_CONFIG=",
|
||||
"_APP_LOGGING_FORMAT=pretty",
|
||||
"_APP_GEO_ENDPOINT=http://appwrite-geo/v1",
|
||||
"_APP_GEO_SECRET=${geo_secret}",
|
||||
"_APP_USAGE_AGGREGATION_INTERVAL=30",
|
||||
"_APP_USAGE_TIMESERIES_INTERVAL=30",
|
||||
"_APP_USAGE_DATABASE_INTERVAL=900",
|
||||
"_APP_WORKER_PER_CORE=6",
|
||||
"_APP_CONSOLE_SESSION_ALERTS=disabled",
|
||||
"_APP_REDIS_HOST=redis",
|
||||
"_APP_REDIS_PORT=6379",
|
||||
"_APP_REDIS_USER=",
|
||||
"_APP_REDIS_PASS=",
|
||||
"_APP_DB_ADAPTER=postgresql",
|
||||
"_APP_DB_HOST=postgresql",
|
||||
"_APP_DB_PORT=5432",
|
||||
"_APP_DB_HOST=mariadb",
|
||||
"_APP_DB_PORT=3306",
|
||||
"_APP_DB_SCHEMA=appwrite",
|
||||
"_APP_DB_USER=user",
|
||||
"_APP_DB_PASS=${db_pw}",
|
||||
"_APP_DATABASE_SHARED_TABLES=",
|
||||
"_APP_DATABASE_SHARED_NAMESPACE=",
|
||||
"_APP_LIMIT_DATABASE_BATCH=100",
|
||||
"_APP_DOCUMENTSDB=enabled",
|
||||
"_APP_DB_ADAPTER_DOCUMENTSDB=mongodb",
|
||||
"_APP_DB_HOST_DOCUMENTSDB=mongodb",
|
||||
"_APP_DB_PORT_DOCUMENTSDB=27017",
|
||||
"_APP_DB_SCHEMA_DOCUMENTSDB=",
|
||||
"_APP_DB_USER_DOCUMENTSDB=",
|
||||
"_APP_DB_PASS_DOCUMENTSDB=",
|
||||
"_APP_CONNECTIONS_DATABASE_DOCUMENTSDB=",
|
||||
"_APP_VECTORSDB=enabled",
|
||||
"_APP_DB_ADAPTER_VECTORSDB=postgresql",
|
||||
"_APP_DB_HOST_VECTORSDB=postgresql",
|
||||
"_APP_DB_PORT_VECTORSDB=5432",
|
||||
"_APP_DB_SCHEMA_VECTORSDB=",
|
||||
"_APP_DB_USER_VECTORSDB=",
|
||||
"_APP_DB_PASS_VECTORSDB=",
|
||||
"_APP_CONNECTIONS_DATABASE_VECTORSDB=",
|
||||
"_APP_EMBEDDING=disabled",
|
||||
"_APP_USAGE_STATS=enabled",
|
||||
"_APP_USAGE_PASS=${usage_pw}",
|
||||
"_APP_CONNECTIONS_DB_USAGE=http://appwrite:${usage_pw}@clickhouse:8123/appwrite",
|
||||
"_APP_POOL_SIZE_USAGE=2",
|
||||
"_APP_CONNECTIONS_DB_EXECUTIONS=http://appwrite:${usage_pw}@clickhouse:8123/appwrite",
|
||||
"_APP_POOL_SIZE_EXECUTIONS=2",
|
||||
"_APP_MAINTENANCE_RETENTION_USAGE_TTL=180",
|
||||
"_APP_STATS_USAGE_QUEUE_NAME=v1-stats-usage",
|
||||
"_APP_STATS_RESOURCES_QUEUE_NAME=v1-stats-resources",
|
||||
"_APP_MAINTENANCE_QUEUE_NAME=v1-maintenance",
|
||||
"_APP_STATS_RESOURCES_INTERVAL=3600",
|
||||
"_APP_ALERTS_INTERVAL=60",
|
||||
"_APP_ALERTS_USAGE_LAG=120",
|
||||
"_APP_MQTT_REPLAY_DEPTH=5",
|
||||
"_APP_MQTT_MAX_PACKET_SIZE=64000",
|
||||
"_APP_LIMIT_REALTIME_SUBSCRIPTIONS=100",
|
||||
"_APP_LIMIT_REALTIME_CHANNELS=100",
|
||||
"_APP_LIMIT_REALTIME_QUERIES=100",
|
||||
"_APP_DB_PASS=password",
|
||||
"_APP_DB_ROOT_PASS=rootsecretpassword",
|
||||
"_APP_INFLUXDB_HOST=influxdb",
|
||||
"_APP_INFLUXDB_PORT=8086",
|
||||
"_APP_STATSD_HOST=telegraf",
|
||||
"_APP_STATSD_PORT=8125",
|
||||
"_APP_SMTP_HOST=",
|
||||
"_APP_SMTP_PORT=",
|
||||
"_APP_SMTP_SECURE=",
|
||||
@@ -141,12 +53,8 @@ env = [
|
||||
"_APP_SMTP_PASSWORD=",
|
||||
"_APP_SMS_PROVIDER=",
|
||||
"_APP_SMS_FROM=",
|
||||
"_APP_SMS_PROJECTS_DENY_LIST=",
|
||||
"_APP_PWNED_PASSWORDS_DSN=none://localhost",
|
||||
"_APP_STORAGE_LIMIT=30000000",
|
||||
"_APP_STORAGE_PREVIEW_LIMIT=20000000",
|
||||
"_APP_AUTOGRAVITY_HOST=http://appwrite-autogravity:8080",
|
||||
"_APP_EXIFTOOL_HOST=http://appwrite-exiftool:8080",
|
||||
"_APP_STORAGE_ANTIVIRUS=disabled",
|
||||
"_APP_STORAGE_ANTIVIRUS_HOST=clamav",
|
||||
"_APP_STORAGE_ANTIVIRUS_PORT=3310",
|
||||
@@ -155,8 +63,6 @@ env = [
|
||||
"_APP_STORAGE_S3_SECRET=",
|
||||
"_APP_STORAGE_S3_REGION=us-east-1",
|
||||
"_APP_STORAGE_S3_BUCKET=",
|
||||
"_APP_STORAGE_S3_ENDPOINT=",
|
||||
"_APP_STORAGE_S3_PATH_STYLE=",
|
||||
"_APP_STORAGE_DO_SPACES_ACCESS_KEY=",
|
||||
"_APP_STORAGE_DO_SPACES_SECRET=",
|
||||
"_APP_STORAGE_DO_SPACES_REGION=us-east-1",
|
||||
@@ -173,318 +79,65 @@ env = [
|
||||
"_APP_STORAGE_WASABI_SECRET=",
|
||||
"_APP_STORAGE_WASABI_REGION=eu-central-1",
|
||||
"_APP_STORAGE_WASABI_BUCKET=",
|
||||
"_APP_COMPUTE_SIZE_LIMIT=30000000",
|
||||
"_APP_COMPUTE_BUILD_SIZE_LIMIT=2000000000",
|
||||
"_APP_COMPUTE_BUILD_TIMEOUT=2700",
|
||||
"_APP_COMPUTE_CPUS=0",
|
||||
"_APP_COMPUTE_MEMORY=0",
|
||||
"_APP_COMPUTE_INACTIVE_THRESHOLD=60",
|
||||
"_APP_COMPUTE_MAINTENANCE_INTERVAL=3600",
|
||||
"_APP_OPEN_RUNTIMES_NFT=enabled",
|
||||
"_APP_FUNCTIONS_SIZE_LIMIT=30000000",
|
||||
"_APP_FUNCTIONS_BUILD_SIZE_LIMIT=2000000000",
|
||||
"_APP_FUNCTIONS_TIMEOUT=900",
|
||||
"_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000",
|
||||
"_APP_FUNCTIONS_SCHEDULE_SPREAD=0",
|
||||
"_APP_FUNCTIONS_RUNTIMES=node-22,node-24,node-26,python-3.12,php-8.3,ruby-3.3,dart-3.5,go-1.23,rust-1.83,bun-1.1,deno-1.46,java-21.0,dotnet-8.0",
|
||||
"_APP_SITES_TIMEOUT=900",
|
||||
"_APP_SITES_RUNTIMES=static-1,node-22",
|
||||
"_APP_EXECUTOR_SECRET=${executor_secret}",
|
||||
"_APP_EXECUTOR_HOST=http://openruntimes-executor/v1",
|
||||
"_APP_EXECUTOR_IMAGES=openruntimes/node:v5-22,openruntimes/python:v5-3.12,openruntimes/php:v5-8.3,openruntimes/static:v5-1",
|
||||
"_APP_EXECUTOR_CONNECTION_STORAGE=local://localhost",
|
||||
"_APP_JOBS_HOST=http://orchestrator:8080",
|
||||
"_APP_JOBS_SECRET=${jobs_secret}",
|
||||
"_APP_JOBS_ENDPOINT=http://appwrite",
|
||||
# Reviews are opt-in: the shared secret turns the /v1/reviews API on, and the
|
||||
# console hides the screen in the self-hosted profile regardless.
|
||||
# "_APP_REVIEWS_SECRET=${reviews_secret}",
|
||||
# "_APP_REVIEWS_MODEL=anthropic/claude-sonnet-4.5",
|
||||
# "_APP_REVIEWS_VERIFY_MODEL=",
|
||||
# "_APP_REVIEWS_AI_GATEWAY_KEY=",
|
||||
"_APP_BROWSER_HOST=http://appwrite-browser:3000/v1",
|
||||
"_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite",
|
||||
"_APP_FUNCTIONS_BUILD_TIMEOUT=900",
|
||||
"_APP_FUNCTIONS_CONTAINERS=10",
|
||||
"_APP_FUNCTIONS_CPUS=0",
|
||||
"_APP_FUNCTIONS_MEMORY=0",
|
||||
"_APP_FUNCTIONS_MEMORY_SWAP=0",
|
||||
"_APP_FUNCTIONS_RUNTIMES=node-16.0,php-8.0,python-3.9,ruby-3.0",
|
||||
"_APP_EXECUTOR_SECRET=your-secret-key",
|
||||
"_APP_EXECUTOR_HOST=http://exc1/v1",
|
||||
"_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes",
|
||||
"_APP_FUNCTIONS_ENVS=node-16.0,php-7.4,python-3.9,ruby-3.0",
|
||||
"_APP_FUNCTIONS_INACTIVE_THRESHOLD=60",
|
||||
"DOCKERHUB_PULL_USERNAME=",
|
||||
"DOCKERHUB_PULL_PASSWORD=",
|
||||
"DOCKERHUB_PULL_EMAIL=",
|
||||
"OPEN_RUNTIMES_NETWORK=appwrite_runtimes",
|
||||
"_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes",
|
||||
"_APP_DOCKER_HUB_USERNAME=",
|
||||
"_APP_DOCKER_HUB_PASSWORD=",
|
||||
"_APP_MAINTENANCE_INTERVAL=86400",
|
||||
"_APP_MAINTENANCE_INACTIVE_INTERVAL=604800",
|
||||
"_APP_MAINTENANCE_START_TIME=00:00",
|
||||
"_APP_MAINTENANCE_RETENTION_CACHE=2592000",
|
||||
"_APP_MAINTENANCE_RETENTION_EXECUTION=1209600",
|
||||
"_APP_MAINTENANCE_RETENTION_AUDIT=1209600",
|
||||
"_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE=15778800",
|
||||
"_APP_MAINTENANCE_RETENTION_ABUSE=86400",
|
||||
"_APP_MAINTENANCE_RETENTION_SCHEDULES=86400",
|
||||
"_APP_MAINTENANCE_RETENTION_ALERTS=7776000",
|
||||
"_APP_INTERVAL_DOMAIN_VERIFICATION=120",
|
||||
"_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300",
|
||||
"_APP_GRAPHQL_INTROSPECTION=enabled",
|
||||
"_APP_GRAPHQL_MAX_BATCH_SIZE=10",
|
||||
"_APP_GRAPHQL_MAX_COMPLEXITY=250",
|
||||
"_APP_GRAPHQL_MAX_DEPTH=4",
|
||||
"_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10",
|
||||
"_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600",
|
||||
"_APP_VCS_GITHUB_APP_NAME=",
|
||||
"_APP_VCS_GITHUB_PRIVATE_KEY=",
|
||||
"_APP_VCS_GITHUB_APP_ID=",
|
||||
"_APP_VCS_GITHUB_CLIENT_ID=",
|
||||
"_APP_VCS_GITHUB_CLIENT_SECRET=",
|
||||
"_APP_VCS_GITHUB_WEBHOOK_SECRET=",
|
||||
"_APP_VCS_GITEA_ENDPOINT=",
|
||||
"_APP_VCS_GITEA_BROWSER_ENDPOINT=",
|
||||
"_APP_VCS_GITEA_CLIENT_ID=",
|
||||
"_APP_VCS_GITEA_CLIENT_SECRET=",
|
||||
"_APP_VCS_GITEA_WEBHOOK_SECRET=",
|
||||
"_APP_VCS_GITLAB_ENDPOINT=",
|
||||
"_APP_VCS_GITLAB_CLIENT_ID=",
|
||||
"_APP_VCS_GITLAB_CLIENT_SECRET=",
|
||||
"_APP_VCS_GITLAB_WEBHOOK_SECRET=",
|
||||
"_APP_VCS_BITBUCKET_CLIENT_ID=",
|
||||
"_APP_VCS_BITBUCKET_CLIENT_SECRET=",
|
||||
"_APP_VCS_BITBUCKET_WEBHOOK_SECRET=",
|
||||
"_APP_VCS_ORIGIN_CLIENT_ID=",
|
||||
"_APP_VCS_ORIGIN_PRIVATE_KEY=",
|
||||
"_APP_VCS_ENTIRE_PLUGIN=",
|
||||
"_APP_VCS_ENTIRE_PRIVATE_KEY=",
|
||||
"_APP_VCS_ENTIRE_ISSUER=",
|
||||
"_APP_VCS_ENTIRE_ENDPOINT=",
|
||||
"_APP_VCS_WEBHOOK_URL=",
|
||||
"_APP_MIGRATION_HOST=appwrite",
|
||||
"_APP_MAINTENANCE_INTERVAL=86400",
|
||||
"_APP_MAINTENANCE_DELAY=0",
|
||||
"_APP_MAINTENANCE_RETENTION_CACHE=2592000",
|
||||
"_APP_MAINTENANCE_RETENTION_EXECUTION=1209600",
|
||||
"_APP_MAINTENANCE_RETENTION_AUDIT=1209600",
|
||||
"_APP_MAINTENANCE_RETENTION_ABUSE=86400",
|
||||
"_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000",
|
||||
"_APP_MAINTENANCE_RETENTION_SCHEDULES=86400",
|
||||
"_APP_GRAPHQL_MAX_BATCH_SIZE=10",
|
||||
"_APP_GRAPHQL_MAX_COMPLEXITY=250",
|
||||
"_APP_GRAPHQL_MAX_DEPTH=3",
|
||||
"_APP_MIGRATIONS_FIREBASE_CLIENT_ID=",
|
||||
"_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=",
|
||||
"_APP_DB_ROOT_PASS=${db_root_pw}",
|
||||
"_APP_DB_USER_PASS=${db_user_pw}",
|
||||
"_APP_DB_MAX_CONNECTIONS=500",
|
||||
"_APP_ASSISTANT_OPENAI_API_KEY=",
|
||||
]
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "mongo-init.js"
|
||||
content = """// mongo-init.js
|
||||
|
||||
const adminDb = db.getSiblingDB('admin');
|
||||
|
||||
const username = process.env.MONGO_INITDB_USERNAME;
|
||||
const password = process.env.MONGO_INITDB_PASSWORD;
|
||||
const database = process.env.MONGO_INITDB_DATABASE;
|
||||
|
||||
// Idempotent: the official entrypoint races the real server on port 27017 when
|
||||
// initialising a fresh volume (appwrite/appwrite#13593), so this script may run
|
||||
// more than once. Skip if the user already exists.
|
||||
if (adminDb.getUser(username) === null) {
|
||||
adminDb.createUser({
|
||||
user: username,
|
||||
pwd: password,
|
||||
roles: [
|
||||
{ role: 'readWrite', db: database }
|
||||
]
|
||||
});
|
||||
}
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "mongo-entrypoint.sh"
|
||||
content = """#!/bin/bash
|
||||
set -e
|
||||
|
||||
KEYFILE_PATH="/data/keyfile/mongo-keyfile"
|
||||
|
||||
if [ ! -f "$KEYFILE_PATH" ]; then
|
||||
echo "Generating random MongoDB keyfile..."
|
||||
mkdir -p /data/keyfile
|
||||
openssl rand -base64 756 > "$KEYFILE_PATH"
|
||||
fi
|
||||
|
||||
chmod 400 "$KEYFILE_PATH"
|
||||
chown mongodb:mongodb "$KEYFILE_PATH" 2>/dev/null || chown 999:999 "$KEYFILE_PATH"
|
||||
|
||||
# The standard entrypoint initialises an empty data directory with a second
|
||||
# mongod on 27017, stopped with `mongod --shutdown`. That returns once the
|
||||
# server clears <dbpath>/mongod.lock, several steps before it exits and
|
||||
# releases the port, and the real server binds a millisecond after it starts --
|
||||
# so a slow shutdown tail leaves it exiting 48, "Address already in use",
|
||||
# taking the container with it (appwrite/appwrite#13593). Initialise here
|
||||
# instead, on a port the real server never binds, and hand over a directory
|
||||
# the standard entrypoint has nothing left to do.
|
||||
INIT_MARKER=/data/db/.appwrite-initialised
|
||||
if [ ! -e "$INIT_MARKER" ]; then
|
||||
INIT_PORT=27018
|
||||
echo "Initialising MongoDB data directory..."
|
||||
find -L /data/db \\! -user mongodb -exec chown mongodb '{}' +
|
||||
export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}"
|
||||
gosu mongodb mongod --dbpath /data/db --bind_ip 127.0.0.1 --port "$INIT_PORT" &
|
||||
INIT_SERVER=$!
|
||||
READY=
|
||||
for _ in $(seq 1 30); do
|
||||
if mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet --eval 'quit(0)' > /dev/null 2>&1; then
|
||||
READY=1
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "$INIT_SERVER" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ -z "$READY" ]; then
|
||||
echo "MongoDB did not accept connections for initialisation" >&2
|
||||
exit 1
|
||||
fi
|
||||
mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet admin --eval '
|
||||
const username = process.env.MONGO_INITDB_ROOT_USERNAME;
|
||||
if (db.getUser(username) === null) {
|
||||
db.createUser({
|
||||
user: username,
|
||||
pwd: process.env.MONGO_INITDB_ROOT_PASSWORD,
|
||||
roles: [{ role: "root", db: "admin" }]
|
||||
});
|
||||
}
|
||||
'
|
||||
mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet "$MONGO_INITDB_DATABASE" /mongo-init.js
|
||||
touch "$INIT_MARKER"
|
||||
kill -TERM "$INIT_SERVER"
|
||||
wait "$INIT_SERVER"
|
||||
echo "MongoDB data directory initialised."
|
||||
fi
|
||||
|
||||
exec docker-entrypoint.sh mongod --replSet rs0 --bind_ip_all --auth --keyFile "$KEYFILE_PATH"
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "clickhouse-config.xml"
|
||||
content = """<?xml version="1.0"?>
|
||||
<!--
|
||||
Retention for ClickHouse's own system log tables.
|
||||
|
||||
Every <ttl> in the image's config.xml sits inside an XML comment, so these
|
||||
tables keep every row forever, and the metric ones add a row per second
|
||||
whether or not the instance serves traffic. Left alone they outgrow the
|
||||
usage data they sit next to and fill the volume.
|
||||
|
||||
When a window here changes, ClickHouse replaces the table on the next
|
||||
restart and keeps the old one, rows and all, as <log>_<N>.
|
||||
clickhouse-init.sh gives those old tables the same window.
|
||||
-->
|
||||
<clickhouse>
|
||||
<!-- The metric, trace and profile logs sample the server every second whether
|
||||
or not Appwrite serves traffic, and nothing reads them back, so their
|
||||
writes and the merges behind them are pure overhead. -->
|
||||
<asynchronous_metric_log remove="1"/>
|
||||
<metric_log remove="1"/>
|
||||
<histogram_metric_log remove="1"/>
|
||||
<query_metric_log remove="1"/>
|
||||
<processors_profile_log remove="1"/>
|
||||
<trace_log remove="1"/>
|
||||
<!-- text_log repeats the server log the container already writes. -->
|
||||
<text_log remove="1"/>
|
||||
|
||||
<!-- Kept for debugging, capped so they cannot fill the volume. -->
|
||||
<asynchronous_insert_log><ttl>event_date + INTERVAL 3 DAY DELETE</ttl></asynchronous_insert_log>
|
||||
<query_log><ttl>event_date + INTERVAL 7 DAY DELETE</ttl></query_log>
|
||||
<query_views_log><ttl>event_date + INTERVAL 7 DAY DELETE</ttl></query_views_log>
|
||||
<query_thread_log><ttl>event_date + INTERVAL 7 DAY DELETE</ttl></query_thread_log>
|
||||
<part_log><ttl>event_date + INTERVAL 7 DAY DELETE</ttl></part_log>
|
||||
<background_schedule_pool_log><ttl>event_date + INTERVAL 7 DAY DELETE</ttl></background_schedule_pool_log>
|
||||
|
||||
<!-- Longer, so it still covers a report that reaches us late. crash_log is
|
||||
deliberately left alone: it is tiny and worth keeping. -->
|
||||
<error_log><ttl>event_date + INTERVAL 14 DAY DELETE</ttl></error_log>
|
||||
</clickhouse>
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "clickhouse-init.sh"
|
||||
content = """#!/bin/bash
|
||||
# The ClickHouse entrypoint sources this file, so a subshell keeps these options out of it.
|
||||
(
|
||||
set -euo pipefail
|
||||
|
||||
client() {
|
||||
clickhouse-client --host 127.0.0.1 --user "$CLICKHOUSE_USER" --password "$CLICKHOUSE_PASSWORD" "$@"
|
||||
}
|
||||
|
||||
# Flushing replaces log tables whose definition changed, keeping each old table as <log>_<N>.
|
||||
client --query 'SYSTEM FLUSH LOGS'
|
||||
|
||||
# Tables left behind never expire on their own: take the TTL of the log that
|
||||
# replaced this one, or a week when the log is no longer written at all.
|
||||
# The named logs must match the ones clickhouse-config.xml removes: a log that
|
||||
# is still written gets replaced on the next restart if its TTL is altered here.
|
||||
client --format TSVRaw <<'SQL' | client --multiquery
|
||||
SELECT concat('ALTER TABLE system.', stale.name, ' MODIFY TTL ', if(active.name = '', 'event_date + INTERVAL 7 DAY DELETE', splitByString(' SETTINGS ', splitByString(' TTL ', active.engine_full)[2])[1]), ';')
|
||||
FROM (
|
||||
SELECT name, splitByChar('_', name)[-1] AS suffix, arrayStringConcat(arrayPopBack(splitByChar('_', name)), '_') AS log
|
||||
FROM system.tables
|
||||
WHERE database = 'system'
|
||||
AND position(engine_full, ' TTL ') = 0
|
||||
AND endsWith(engine, 'MergeTree')
|
||||
AND name IN (SELECT table FROM system.columns WHERE database = 'system' AND name = 'event_date' AND type = 'Date')
|
||||
) AS stale
|
||||
LEFT JOIN system.tables AS active
|
||||
ON active.database = 'system'
|
||||
AND active.name = stale.log
|
||||
AND position(active.engine_full, ' TTL ') > 0
|
||||
WHERE (endsWith(stale.log, '_log') AND toUInt64OrNull(stale.suffix) IS NOT NULL)
|
||||
OR stale.name IN ('asynchronous_metric_log', 'metric_log', 'histogram_metric_log', 'query_metric_log', 'processors_profile_log', 'trace_log', 'text_log')
|
||||
SQL
|
||||
)
|
||||
"""
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite-console"
|
||||
port = 3000
|
||||
serviceName = "appwrite"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
path = "/"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite"
|
||||
serviceName = "appwrite-console"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
path = "/v1"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
path = "/.well-known"
|
||||
path = "/console"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite-realtime"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
path = "/v1/realtime"
|
||||
|
||||
# --- Dedicated Console domain ---
|
||||
# The Console SPA is served at the root of ${console_domain}, with /v1 and
|
||||
# /v1/realtime proxied to the API and Realtime so the Console reaches the API
|
||||
# on its own origin.
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite-console"
|
||||
port = 3000
|
||||
host = "${console_domain}"
|
||||
path = "/"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite"
|
||||
port = 80
|
||||
host = "${console_domain}"
|
||||
path = "/v1"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite-realtime"
|
||||
port = 80
|
||||
host = "${console_domain}"
|
||||
path = "/v1/realtime"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite"
|
||||
port = 80
|
||||
host = "${sites_domain}"
|
||||
path = "/"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite"
|
||||
port = 80
|
||||
host = "${functions_domain}"
|
||||
path = "/"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "appwrite-mqtt"
|
||||
port = 8083
|
||||
host = "${mqtt_domain}"
|
||||
path = "/"
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "aptabase",
|
||||
"name": "Aptabase",
|
||||
"version": "v1.0.0",
|
||||
"description": "Aptabase is a self-hosted web analytics platform that lets you track website traffic and user behavior.",
|
||||
"logo": "aptabase.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/aptabase/aptabase",
|
||||
"website": "https://aptabase.com/",
|
||||
"docs": "https://github.com/aptabase/aptabase/blob/main/README.md"
|
||||
},
|
||||
"tags": [
|
||||
"analytics",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
arangodb:
|
||||
image: arangodb:3.12.4
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
ports:
|
||||
- 8529
|
||||
environment:
|
||||
- ARANGO_ROOT_PASSWORD=${ARANGO_PASSWORD}
|
||||
@@ -10,4 +11,4 @@ services:
|
||||
- data:/var/lib/arangodb3
|
||||
|
||||
volumes:
|
||||
data: {}
|
||||
data: {}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "arangodb",
|
||||
"name": "ArangoDB",
|
||||
"version": "latest",
|
||||
"description": "ArangoDB is a native multi-model database with flexible data models for documents, graphs, and key-values. Build high performance applications using a convenient SQL-like query language or JavaScript extensions.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/arangodb/arangodb",
|
||||
"website": "https://www.arangodb.com/",
|
||||
"docs": "https://www.arangodb.com/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"database",
|
||||
"graph-database",
|
||||
"nosql"
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1152)"><rect width="1000" height="1000" fill="#000000"></rect><g transform="matrix(3.401360544217687,0,0,3.401360544217687,250.00000000000003,265.30612244897964)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="147" height="138" viewBox="0 0 147 138"><image width="147" height="138" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJMAAACKCAYAAACuPHhGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAZgSURBVHgB7Z3rddNAEIVHVBA6MB1AB04H6QCnAuiAQwehgogOoALUAXSAO0g6MBqzCvip1+zundX9zvEJIfkRS1efVrM760oCu91u0355FELGUVdVda//eCWEGMEwETMYJmIGw0TMYJiIGQwTMYNhImYwTMQMhomYwTARMxgmYgbDRMxgmIgZDBMxg2EiZjBMxAyGiZjBMBEzGCZiBsNEzGCYiBkMEzGDYSJmMEzEDIaJmMEwETMYJmIGw0TMYJiIGQwTMYNhImZUUgi73W7VfvktvnhuX2+qqnqWAijJTB/EHzft66MUQhFmClb60b5W4o9i7FSKmd6LzyApxdjJvZlaK+nJ+Cl+w6QUYacSzHQnvoOkFGGnEsykT3Ar8Y97O7k2U9hueiVl4N5Ors1UkJU6nlszvRanuDVTYVbquAnvyyVuzVSglTq2rZ3eiENcmqlQK3WsvNrJpZkKtlJH09rpVpzhzkyFW6lj3b7PtTjDnZkWYKUOd3ZyZaaFWKnDnZ283ebey7L4JI5wE6Zwla5lWbiykyczubpKDXHzvl2EaaFW6nBjJy9mWtpY6RgXdoIvDThtFIjBbVsqaAQYD2Za6ljpGPjjAG0mWukEXTy3FVDQzUQrHQJ9PGDNRCtdBNZOyGailc4Du7QX0ky00lVgGw9QzUQrXQa28QDOTLTSICDthGgmWqkfSDtBmcn5BhSpgbMTmplQN6D4JnjA2QnGTOBW0tajR8FbuQBlJyQzrQUzSHUoEn4VPKDshGQm1EaBfcU5bN2jf+ONYAFjJwgzATcKdFaScLK+CB4wdoIwE7qVum9op+tkNxOwlZrjCVVwO91JZrKbCdhKZ1c2Alfos294kdVMwFbaXloiG2zVCB7ZN7zIfZtDnTr5PPPnuci6F3q2MIFbqb72C8FajeDxNmdbVE4zebXS2N9LTbbjmiVMyFaSgfNwwU6/BI9sTZu5zITaVNmMrNUgTrEoWeyUvDQQrpofgsmoxfrARUzltkrctJnDTKhjpboa2fUBXMRUkh/npGYqyUodtNM/UpsJdaxUVxN70YKdOHaShGYCbxSYdQWX/N7GkNJMqGOlZu7BBp5iUZId9yRhClfuRjCxukWhFjGT1Z1SmQnVSr1TJ0MBnmJRkoxVo4+ZwMcT91ZhUkp8Wh1DCjMVb6WOYKetYBL9PEQNE/hYKVaxEbWIuQk1sWjENhNyq3esxspa/q7JRiRq40G0MIFbqY41fgCfYvkQ004xzYRspdiP8Q+CaaeobVFRwhSstBZM6thPNeBTLNHsFMtMqBtQKKmKi7VgEs1O5nUm8A0okn6GW3ss9DisBY8oTZsxzKTNgCvBJPWUB+oUSxQ7xTATalNllibFJdnJ1Ezgn1SZyxKoZQJzO5maiVY6T3tcngRzJaapnczMRCtdBdlOGzHCzEzIVmpf76qM282ArxM3s7aJmcCt1FSZ9y0Cn2Ix2/DCxEzAVlKir+MZwhLsNNtM4FaqEYKkBDt9F0xWFkt7Z5uJVhoO+ErM2bMDs8xEK40DfJ347MaDube5rJtL9YA6a486xaLMWjY0OUwhxW8Fk9m9cLEo2U5zzIS8+A3VSh3If9/k8zppAA4+kMy+6+wQgKdYlEkt5VPNhDxWQh6T/A9qEVOZZKfRZgJvqnRhJQW8iKmMttMUMyGPlZCv9gPAp1iU0ed5lJkcfH4uVJGyj2CnJ8FllJ3GmgnZSnBFyj7Au1iUURteDDYTrRQH8CdjZfBxHWMmWikC4EVMZfB5H2QmWikuDuz0esiasKFmQrZS4zlIigM7DWo86DWTAytNqtaiEVZgPAomgxoPhpgJ2UrbEoIU+Ca4W/EMaou6GibwbXEUL1MnvTgoYvZueNFnpiQba07EfBtBAB7EsZ0uholWSo93O10zE/K2OFvBfvqZw4PgctVO18K0EVzclwMuEezUCC4X7XQ2TOCNAkpxt7gjkN/fRTudrTOBty/p1Mm9FA7wVjzK2brTiZloJRjQ7bQ5/s8TM9FKOLTn4qfgdgCdrGo9MJMDK6F3nViD/H5PNrw4MBO4lZJuboqAg3XiB3Z6MROthIeDIqba6a775sVM4FZy03VijQM7vdwx9mbiExwuDtaJv7SU781EK2HjYE3Z3k6vQqpWgsvixkrHgH+gtLK3UwVeaVVcr++2wsE68eYPiYOoQetKl74AAAAASUVORK5CYII="></image></svg></g></g><defs><clipPath id="SvgjsClipPath1152"><rect width="1000" height="1000" x="0" y="0" rx="350" ry="350"></rect></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,100 +0,0 @@
|
||||
# Arche — AI workspace platform that spawns per-user OpenCode containers
|
||||
# via a Docker socket proxy.
|
||||
#
|
||||
# PRE-REQUISITES (run once on the VPS before deploying):
|
||||
# mkdir -p /opt/arche/kb-content /opt/arche/kb-config /opt/arche/users
|
||||
#
|
||||
# Optional (only needed to seed the Knowledge Base):
|
||||
# git init --bare --initial-branch=main /opt/arche/kb-content
|
||||
# git init --bare --initial-branch=main /opt/arche/kb-config
|
||||
#
|
||||
# POST-DEPLOY (set in Dokploy → Environment, then redeploy):
|
||||
# 1. Deploy this template once. Dokploy auto-creates a network for the
|
||||
# compose stack.
|
||||
# 2. SSH into the host and find the network name:
|
||||
# docker network ls --format '{{.Name}}' | grep arche
|
||||
# It looks like `<stack-name>_default` or similar.
|
||||
# 3. Add the env var OPENCODE_NETWORK=<that-network-name> and redeploy.
|
||||
# Without it, spawned user workspaces will fail to start.
|
||||
#
|
||||
# IMAGE ARCHITECTURE:
|
||||
# Default tags target linux/amd64. For ARM64 hosts (Apple Silicon,
|
||||
# Ampere, Graviton), override both images to the `-arm64` variants in
|
||||
# Dokploy → Environment:
|
||||
# ARCHE_WEB_IMAGE=ghcr.io/peaberry-studio/arche/web:latest-arm64
|
||||
# ARCHE_WORKSPACE_IMAGE=ghcr.io/peaberry-studio/arche/workspace:latest-arm64
|
||||
|
||||
services:
|
||||
web:
|
||||
image: ${ARCHE_WEB_IMAGE:-ghcr.io/peaberry-studio/arche/web:latest}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
docker-socket-proxy:
|
||||
condition: service_started
|
||||
environment:
|
||||
ARCHE_DOMAIN: ${ARCHE_DOMAIN}
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/arche?schema=public
|
||||
|
||||
ARCHE_SESSION_PEPPER: ${ARCHE_SESSION_PEPPER}
|
||||
ARCHE_ENCRYPTION_KEY: ${ARCHE_ENCRYPTION_KEY}
|
||||
ARCHE_INTERNAL_TOKEN: ${ARCHE_INTERNAL_TOKEN}
|
||||
ARCHE_GATEWAY_TOKEN_SECRET: ${ARCHE_GATEWAY_TOKEN_SECRET}
|
||||
ARCHE_CONNECTOR_OAUTH_STATE_SECRET: ${ARCHE_CONNECTOR_OAUTH_STATE_SECRET}
|
||||
|
||||
ARCHE_GATEWAY_TOKEN_TTL_SECONDS: "86400"
|
||||
ARCHE_GATEWAY_BASE_URL: http://web:3000
|
||||
ARCHE_PUBLIC_BASE_URL: https://${ARCHE_DOMAIN}
|
||||
ARCHE_SESSION_TTL_DAYS: "7"
|
||||
ARCHE_COOKIE_SECURE: "true"
|
||||
|
||||
ARCHE_SEED_ADMIN_EMAIL: ${ARCHE_SEED_ADMIN_EMAIL}
|
||||
ARCHE_SEED_ADMIN_PASSWORD: ${ARCHE_SEED_ADMIN_PASSWORD}
|
||||
ARCHE_SEED_ADMIN_SLUG: ${ARCHE_SEED_ADMIN_SLUG:-admin}
|
||||
|
||||
CONTAINER_PROXY_HOST: docker-socket-proxy
|
||||
CONTAINER_PROXY_PORT: "2375"
|
||||
OPENCODE_IMAGE: ${ARCHE_WORKSPACE_IMAGE:-ghcr.io/peaberry-studio/arche/workspace:latest}
|
||||
OPENCODE_NETWORK: ${OPENCODE_NETWORK}
|
||||
|
||||
KB_CONTENT_HOST_PATH: /opt/arche/kb-content
|
||||
KB_CONFIG_HOST_PATH: /opt/arche/kb-config
|
||||
ARCHE_USERS_PATH: /opt/arche/users
|
||||
expose:
|
||||
- "3000"
|
||||
volumes:
|
||||
- /opt/arche/kb-content:/kb-content
|
||||
- /opt/arche/kb-config:/kb-config
|
||||
- /opt/arche/users:/opt/arche/users
|
||||
|
||||
postgres:
|
||||
image: postgres:16
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: arche
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
docker-socket-proxy:
|
||||
image: ghcr.io/tecnativa/docker-socket-proxy:master
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
CONTAINERS: 1
|
||||
NETWORKS: 1
|
||||
IMAGES: 1
|
||||
INFO: 1
|
||||
POST: 1
|
||||
VOLUMES: 1
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "arche",
|
||||
"name": "Arche",
|
||||
"version": "latest",
|
||||
"description": "Arche is a self-hosted AI workspace platform that spawns per-user OpenCode coding containers backed by a shared Knowledge Base. Requires the `arche-internal` external Docker network and `/opt/arche/{kb-content,kb-config,users}` directories on the host before deploy. Images are linux/amd64 by default; override `ARCHE_WEB_IMAGE` and `ARCHE_WORKSPACE_IMAGE` to the `-arm64` tag variants for ARM64 hosts.",
|
||||
"logo": "arche.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/peaberry-studio/arche",
|
||||
"website": "https://github.com/peaberry-studio/arche",
|
||||
"docs": "https://github.com/peaberry-studio/arche#readme"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"coding",
|
||||
"workspace",
|
||||
"self-hosted",
|
||||
"llm"
|
||||
]
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_password = "${password:32}"
|
||||
session_pepper = "${base64:32}"
|
||||
encryption_key = "${base64:32}"
|
||||
internal_token = "${base64:32}"
|
||||
gateway_token_secret = "${base64:32}"
|
||||
connector_oauth_state_secret = "${base64:32}"
|
||||
seed_admin_email = "${email}"
|
||||
seed_admin_password = "${password:16}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"ARCHE_DOMAIN=${main_domain}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"ARCHE_SESSION_PEPPER=${session_pepper}",
|
||||
"ARCHE_ENCRYPTION_KEY=${encryption_key}",
|
||||
"ARCHE_INTERNAL_TOKEN=${internal_token}",
|
||||
"ARCHE_GATEWAY_TOKEN_SECRET=${gateway_token_secret}",
|
||||
"ARCHE_CONNECTOR_OAUTH_STATE_SECRET=${connector_oauth_state_secret}",
|
||||
"ARCHE_SEED_ADMIN_EMAIL=${seed_admin_email}",
|
||||
"ARCHE_SEED_ADMIN_PASSWORD=${seed_admin_password}",
|
||||
"ARCHE_SEED_ADMIN_SLUG=admin",
|
||||
"ARCHE_WEB_IMAGE=ghcr.io/peaberry-studio/arche/web:latest",
|
||||
"ARCHE_WORKSPACE_IMAGE=ghcr.io/peaberry-studio/arche/workspace:latest",
|
||||
"OPENCODE_NETWORK=",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "web"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<rect width="128" height="128" rx="28" fill="#101828"/>
|
||||
<circle cx="64" cy="64" r="34" fill="#38bdf8" opacity="0.92"/>
|
||||
<text x="64" y="74" text-anchor="middle" font-family="Arial, sans-serif" font-size="30" font-weight="700" fill="#ffffff">A</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 325 B |