diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 67f7c6a..eae2ec0 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -213,3 +213,50 @@ 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.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 \ + --clobber + echo "Uploaded docker-compose.release.yml and env.example to release v${VERSION}" diff --git a/README.md b/README.md index 2cd13f5..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)
@@ -60,7 +64,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 ``` 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