From 2d6b96db343cc7d4fcf7259e894cb5f9937c5ea2 Mon Sep 17 00:00:00 2001 From: Kerem Can ONEMLI Date: Tue, 26 May 2026 22:38:16 +0000 Subject: [PATCH 1/3] Ship docker-compose.release.yml and .env.example as release assets The README quickstart points users at 'https://github.com/onemli/fabrik/releases/latest/download/docker-compose.release.yml' and the matching '.env.example', but neither file was ever attached to a release. New installs that followed the README hit a 404. Two changes fix that: 1. docker-compose.release.yml: a standalone release-time compose file that pulls pre-built images from Docker Hub. No build context, no source tree required. The image tag defaults to FABRIK_VERSION or 'latest' if that's not set; CI overrides the default per release. 2. .github/workflows/docker-build.yml: new attach-release-assets job that runs on tag pushes, bakes the tag version into docker-compose.release.yml as the default FABRIK_VERSION, and uploads both docker-compose.release.yml and .env.example to the existing GitHub Release with 'gh release upload --clobber'. The release itself is still created manually beforehand with curated notes. This job only attaches the two files to it. --- .github/workflows/docker-build.yml | 41 +++++ docker-compose.release.yml | 232 +++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 docker-compose.release.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 67f7c6a..3b1abcf 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -213,3 +213,44 @@ jobs: sleep $((attempt * 5)) done done + + attach-release-assets: + name: Attach release assets + runs-on: ubuntu-latest + needs: [build-backend, build-frontend] + if: startsWith(github.ref, 'refs/tags/v') + permissions: + # contents: write so gh release upload can attach files to the + # GitHub Release for this tag. The release itself is created + # manually beforehand with curated notes; this job only adds + # the docker-compose.release.yml and .env.example files. + contents: write + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Bake version into docker-compose.release.yml + # Replaces `${FABRIK_VERSION:-latest}` with `${FABRIK_VERSION:-}` + # so the compose file shipped with v1.2.3 defaults to + # onemli/fabrik-*:1.2.3 without losing the env-var override path. + run: | + set -euo pipefail + VERSION="${GITHUB_REF#refs/tags/v}" + sed -i "s|\${FABRIK_VERSION:-latest}|\${FABRIK_VERSION:-${VERSION}}|g" docker-compose.release.yml + echo "Pinned default FABRIK_VERSION to ${VERSION}:" + grep "FABRIK_VERSION" docker-compose.release.yml + + - name: Upload compose and env files to release + # Fails clearly if the release doesn't yet exist for this tag — + # the expectation is that `gh release create v` ran first. + # `--clobber` lets re-runs replace previously-uploaded copies. + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + VERSION="${GITHUB_REF#refs/tags/v}" + gh release upload "v${VERSION}" \ + docker-compose.release.yml \ + .env.example \ + --clobber + echo "Uploaded docker-compose.release.yml and .env.example to release v${VERSION}" diff --git a/docker-compose.release.yml b/docker-compose.release.yml new file mode 100644 index 0000000..9f970e2 --- /dev/null +++ b/docker-compose.release.yml @@ -0,0 +1,232 @@ +# Fabrik — release-time docker-compose +# +# This is the compose file users download from a GitHub release. It pulls +# pre-built images from Docker Hub (no local build context, no source tree +# required) and is meant to be paired with a release-managed .env. +# +# Pin a specific version by exporting FABRIK_VERSION before bringing the +# stack up, e.g.: +# echo "FABRIK_VERSION=1.2.1" >> .env +# docker compose pull +# docker compose up -d +# +# The default is `latest`, which floats with whatever the most recent +# successful build pushed. + +services: + postgres: + image: postgres:17-alpine + container_name: fabrik-postgres + mem_limit: 512m + env_file: + - .env + environment: + - TZ=${TZ:-UTC} + - POSTGRES_DB=${POSTGRES_DB:-fabrik} + - POSTGRES_USER=${POSTGRES_USER:-fabrik} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + ports: + - "127.0.0.1:${POSTGRES_EXTERNAL_PORT:-5432}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - fabrik-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fabrik"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:8-alpine + container_name: fabrik-redis + mem_limit: 256m + env_file: + - .env + environment: + - TZ=${TZ:-UTC} + ports: + - "127.0.0.1:${REDIS_EXTERNAL_PORT:-6379}:6379" + volumes: + - redis_data:/data + networks: + - fabrik-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + neo4j: + image: neo4j:5.26 + container_name: fabrik-neo4j + mem_limit: 3g + environment: + - TZ=${TZ:-UTC} + - NEO4J_AUTH=neo4j/${NEO4J_PASSWORD:?NEO4J_PASSWORD is required} + - NEO4J_server_memory_pagecache_size=${NEO4J_PAGECACHE_SIZE:-512M} + - NEO4J_server_memory_heap_initial__size=1G + - NEO4J_server_memory_heap_max__size=${NEO4J_HEAP_MAX_SIZE:-2G} + - NEO4J_ACCEPT_LICENSE_AGREEMENT=yes + - NEO4J_dbms_memory_transaction_total_max=${NEO4J_TX_MEMORY_MAX:-1G} + ports: + - "127.0.0.1:${NEO4J_HTTP_PORT:-7474}:7474" + - "127.0.0.1:${NEO4J_BOLT_PORT:-7687}:7687" + restart: unless-stopped + volumes: + - neo4j_data:/data + - neo4j_logs:/logs + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:7474 >/dev/null || exit 1"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + networks: + - fabrik-network + + backend: + image: onemli/fabrik-backend:${FABRIK_VERSION:-latest} + container_name: fabrik-backend + mem_limit: 512m + # Uses the image CMD (entrypoint.sh): migrate -> bootstrap_mim -> daphne. + volumes: + - fabrik_mim_cache:/app/mim_cache + expose: + - "8000" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + neo4j: + condition: service_healthy + env_file: + - .env + environment: + - TZ=${TZ:-UTC} + - NEO4J_URI=${NEO4J_URI:-bolt://neo4j:7687} + - NEO4J_USER=${NEO4J_USER:-neo4j} + - NEO4J_PASSWORD=${NEO4J_PASSWORD:?NEO4J_PASSWORD is required} + - DATABASE_URL=${DATABASE_URL:?DATABASE_URL is required} + - CELERY_BROKER_URL=${CELERY_BROKER_URL:?CELERY_BROKER_URL is required} + - CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND:?CELERY_RESULT_BACKEND is required} + - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY:?DJANGO_SECRET_KEY is required} + - ENCRYPTION_KEY=${ENCRYPTION_KEY:?ENCRYPTION_KEY is required} + - DEBUG=${DEBUG:-False} + - ALLOWED_HOSTS=${ALLOWED_HOSTS:?ALLOWED_HOSTS is required} + - DEFAULT_USER_TIMEZONE=${DEFAULT_USER_TIMEZONE:-UTC} + restart: unless-stopped + networks: + - fabrik-network + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + + celery-worker: + image: onemli/fabrik-backend:${FABRIK_VERSION:-latest} + container_name: fabrik-celery-worker + mem_limit: 2g + command: celery -A fabrik worker --loglevel=info --concurrency=${CELERY_WORKER_CONCURRENCY:-2} -Q celery,query_exec,scheduled,awx_monitor,awx_exec,maintenance,mim_import + volumes: + - fabrik_mim_cache:/app/mim_cache + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + neo4j: + condition: service_started + env_file: + - .env + environment: + - TZ=${TZ:-UTC} + - NEO4J_URI=${NEO4J_URI:-bolt://neo4j:7687} + - NEO4J_USER=${NEO4J_USER:-neo4j} + - NEO4J_PASSWORD=${NEO4J_PASSWORD:?NEO4J_PASSWORD is required} + - DATABASE_URL=${DATABASE_URL:?DATABASE_URL is required} + - CELERY_BROKER_URL=${CELERY_BROKER_URL:?CELERY_BROKER_URL is required} + - CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND:?CELERY_RESULT_BACKEND is required} + - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY:?DJANGO_SECRET_KEY is required} + - ENCRYPTION_KEY=${ENCRYPTION_KEY:?ENCRYPTION_KEY is required} + - DEFAULT_USER_TIMEZONE=${DEFAULT_USER_TIMEZONE:-UTC} + restart: unless-stopped + networks: + - fabrik-network + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + + celery-beat: + image: onemli/fabrik-backend:${FABRIK_VERSION:-latest} + container_name: fabrik-celery-beat + mem_limit: 256m + command: celery -A fabrik beat --loglevel=info + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + env_file: + - .env + environment: + - TZ=${TZ:-UTC} + - NEO4J_URI=${NEO4J_URI:-bolt://neo4j:7687} + - NEO4J_USER=${NEO4J_USER:-neo4j} + - NEO4J_PASSWORD=${NEO4J_PASSWORD:?NEO4J_PASSWORD is required} + - DATABASE_URL=${DATABASE_URL:?DATABASE_URL is required} + - CELERY_BROKER_URL=${CELERY_BROKER_URL:?CELERY_BROKER_URL is required} + - CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND:?CELERY_RESULT_BACKEND is required} + - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY:?DJANGO_SECRET_KEY is required} + - ENCRYPTION_KEY=${ENCRYPTION_KEY:?ENCRYPTION_KEY is required} + - DEFAULT_USER_TIMEZONE=${DEFAULT_USER_TIMEZONE:-UTC} + restart: unless-stopped + networks: + - fabrik-network + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + + frontend: + image: onemli/fabrik-frontend:${FABRIK_VERSION:-latest} + container_name: fabrik-frontend + mem_limit: 256m + # nginx serves the built SPA and proxies /api, /admin, /static, /ws to backend. + ports: + - "${FRONTEND_PORT:-80}:80" + depends_on: + - backend + environment: + - TZ=${TZ:-UTC} + restart: unless-stopped + networks: + - fabrik-network + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + +volumes: + postgres_data: + name: fabrik_postgres_data + redis_data: + name: fabrik_redis_data + neo4j_data: + name: fabrik_neo4j_data + neo4j_logs: + name: fabrik_neo4j_logs + fabrik_mim_cache: + name: fabrik_mim_cache + +networks: + fabrik-network: + name: fabrik-network + driver: bridge From 1312938b8b8f07e28d6ad91e5da7f4479b063d68 Mon Sep 17 00:00:00 2001 From: Kerem Can ONEMLI Date: Tue, 26 May 2026 22:41:26 +0000 Subject: [PATCH 2/3] Upload env.example without leading dot GitHub release asset storage strips leading dots from filenames and serves dotfiles under a 'default.' prefix, so .env.example uploaded as-is appeared on releases as default.env.example, which broke the README's quickstart curl URL. The workflow now copies .env.example to env.example before upload. The README curl URL drops the leading dot in the asset name but the local filename stays .env.example (the -o flag controls where it lands on disk). --- .github/workflows/docker-build.yml | 10 ++++++++-- README.md | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 3b1abcf..eae2ec0 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -244,13 +244,19 @@ jobs: # Fails clearly if the release doesn't yet exist for this tag — # the expectation is that `gh release create v` ran first. # `--clobber` lets re-runs replace previously-uploaded copies. + # + # `.env.example` is copied to `env.example` (no leading dot) + # before upload because GitHub's release asset storage strips + # leading dots and serves dotfiles as `default.env.example`, + # which breaks the README's curl URL. env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail VERSION="${GITHUB_REF#refs/tags/v}" + cp .env.example env.example gh release upload "v${VERSION}" \ docker-compose.release.yml \ - .env.example \ + env.example \ --clobber - echo "Uploaded docker-compose.release.yml and .env.example to release v${VERSION}" + echo "Uploaded docker-compose.release.yml and env.example to release v${VERSION}" diff --git a/README.md b/README.md index 2cd13f5..2f3a56c 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ You need Docker 24+ and Docker Compose v2. That's it. ```bash mkdir fabrik && cd fabrik curl -fLo docker-compose.yml https://github.com/onemli/fabrik/releases/latest/download/docker-compose.release.yml -curl -fLo .env.example https://github.com/onemli/fabrik/releases/latest/download/.env.example +curl -fLo .env.example https://github.com/onemli/fabrik/releases/latest/download/env.example cp .env.example .env ``` From cb93a1bd2c6f96f05b82abf825474d84a37aa7b6 Mon Sep 17 00:00:00 2001 From: Kerem Can ONEMLI Date: Tue, 2 Jun 2026 09:03:43 +0000 Subject: [PATCH 3/3] Add DevNet Code Exchange badges to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2f3a56c..fe7b45e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ [![Docker Frontend](https://img.shields.io/docker/pulls/onemli/fabrik-frontend?style=flat-square&label=frontend%20pulls&color=2563eb&logo=docker&logoColor=white)](https://hub.docker.com/r/onemli/fabrik-frontend) [![Docs](https://img.shields.io/badge/docs-fabrikops.com-7c3aed?style=flat-square)](https://docs.fabrikops.com/fabrik/) +[![published](https://static.production.devnetcloud.com/codeexchange/assets/images/devnet-published.svg)](https://developer.cisco.com/codeexchange/github/repo/onemli/fabrik) +[![Run in Cisco Cloud IDE](https://static.production.devnetcloud.com/codeexchange/assets/images/devnet-runable-icon.svg)](https://developer.cisco.com/codeexchange/devenv/onemli/fabrik/) +[![Cisco Developed](https://static.production.devnetcloud.com/codeexchange/assets/images/cisco-developed.svg)](https://developer.cisco.com/codeexchange/github/repo/onemli/fabrik) + [**Docs**](https://docs.fabrikops.com/fabrik/) · [**Quickstart**](https://docs.fabrikops.com/fabrik/getting-started/) · [**Releases**](https://github.com/onemli/fabrik/releases) · [**Discussions**](https://github.com/onemli/fabrik/discussions) · [**Report a bug**](https://github.com/onemli/fabrik/issues)