Hey, It Works! Logo
Hey, It Works!

Tech Blog by Daniel Rosehill

My AI Stack - March 2025

My AI Stack - March 2025

As we approach the midpoint of 2025, the AI landscape continues to evolve at a dizzying pace. What worked six months ago might already be outdated, and new tools emerge almost weekly. Today (March 14th, 2025), I'm sharing my current AI stack — the collection of tools, services, and workflows that I've found most effective for my daily use.

This is very much a snapshot in time. The beauty (and challenge) of working with AI in 2025 is that everything is subject to ongoing change and evolution as the underlying technologies improve and new approaches emerge. Consider this less of a recommendation and more of a field report from someone deep in the AI trenches.

I've also published this stack as a GitHub repository for those who want to track changes over time or perhaps fork it as a starting point for documenting their own AI toolkit.

Quick Navigation

Core AI Components

Navigate this section: LLM APIs | LLM Frontend | Speech To Text | Vector Storage | Regular Storage

These are the foundational elements that support most of my AI-related activities.

LLM APIs

While self-hosting LLMs is increasingly viable, I still prefer using cloud APIs for most of my needs. This approach lets me avoid hardware stress and simplifies deployment while giving me access to state-of-the-art models.

My current favorites:

  • OpenRouter has become my go-to for consolidated billing and access to a wide variety of APIs. It lets me select models best suited for specific tasks without managing multiple accounts.

  • Google Flash 2.0 serves as my primary model due to its fast inference, large context window, and reasonable pricing. While not the best for complex reasoning, its versatility makes it suitable as the backing model for all my Assistant configurations.

  • Qwen's models, particularly Qwen coder, are my preference for non-agentic code generation and debugging. I find these models particularly underrated in the current landscape.

  • Cohere excels at instructional text-based tasks where clarity and precision matter.

  • OpenAI still has its place in my toolkit, especially Sonnet 3.7 for agented code generation, though I've found its recent performance somewhat inconsistent.

LLM Frontend

After testing numerous AI tool frontends, Open Web UI stands out as the most impressive for my needs. I've shared some of my configurations on the Open Web UI community platform.

One lesson learned: if you're planning to use these tools long-term, start with PostgreSQL rather than SQLite. While the container defaults to Chroma DB, you can configure it to use Milvus, Qdrant, or other vector database options.

My approach to self-hosting has evolved from experimentation to building for long-term stability, with careful component selection from the outset.

Speech To Text / ASR

Transitioning to speech-to-text has been transformative for my workflow! After unsatisfactory experiences a decade ago, Whisper has revolutionized reliability and made STT good enough for everyday use.

I use Whisper AI as a Chrome extension for speech-to-text and rely on it for many hours each day. For Android, the open source Futo Keyboard project shows promise, though it depends on local hardware capabilities.

While I recognize the use case for local processing, I generally prefer not to run speech-to-text or most AI models locally. On my Linux desktop, I use generative AI tools to create custom notepads that leverage Whisper via API.

Vector Storage

I'm currently developing a personal managed context data store for creating personalized AI experiences. This is a long-term project, and my approach will likely evolve significantly over time.

My current focus is on using multi-agent workflows to proactively generate contextual data. Some of my related projects include:

The project involves creating markdown files based on interviews detailing aspects of my life. I've also experimented with the inverse approach of putting non-contextual data through an LLM pipeline to isolate context data.

For vector storage itself, I avoid OpenAI assistants to prevent vendor lock-in and instead use Qdrant to decouple my personal context data from other parts of the project.

Regular Storage

Contrary to what some might expect, storing AI outputs robustly doesn't require specialized solutions; regular databases work perfectly well. MongoDB and PostgreSQL are my preferred databases, with PostgreSQL being especially beneficial as it can easily be extended with PGVector for vector capabilities when needed.

Agentic, Generative & Orchestration Tools

Navigate this section: Agents & Assistants | Other Generative AI | Workflows & Orchestration | AI IDEs & Computer Use

These tools extend the capabilities of my core components, enabling more complex workflows and creative applications.

Agents & Assistants

I've explored many AI agents and assistants, noting that many interesting projects lack well-developed frontends. You can explore some of my AI assistants in my AI Assistants Library.

I'm a strong advocate for simple system prompt-based agents and have open-sourced over 600 system prompts since discovering AI in early 2024. I currently use these in Open Web UI, sharing my library with that community.

While having 600+ assistants might seem excessive, it's quite manageable when each assistant is highly focused on a small, distinct task. For example, I have assistants for changing the persona of text, formalizing it, informalizing it, and other common writing tasks.

Other Generative AI

Beyond text generation, I use:

  • Leonardo AI for text-to-image generation, appreciating its diversity of models and configurable parameters.
  • Runway ML for creating animations from frames, though I haven't explored text-to-video as extensively yet.

Workflows & Orchestration

My main interest in AI systems lies in addressing the challenge of making this rapidly growing technology more effective through tool use, workflow management, and orchestration.

I use N8N to provision and orchestrate agents, experimenting with different stack combinations while prioritizing simplicity and fewer components. I also appreciate the pipelines and tools within Open Web UI that enable actions on external services.

Langflow provides a user-friendly interface for visually building complex workflows with language models, making it easier to prototype and experiment with different LLM configurations.

AI IDEs & Computer Use

I currently subscribe to Windsurf, valuing its integrated experience for agent-driven code generation, despite some recent performance issues. I also use Aider, especially for single-script projects where precise context specification is advantageous.

My daily driver is OpenSUSE Linux, which influences my choice of tools. I've found Open Interpreter impressive for running LLMs directly within the terminal and see significant potential in this project, though it requires careful provisioning for debugging and working directly on the computer.

Docker Implementation

My GitHub repository includes a docker-compose.yaml file that encapsulates my AI stack. This setup allows for easy deployment and management of the various components.

Key components include:

  • OpenWebUI: My primary frontend for interacting with LLMs
  • PostgreSQL: The main database for storing application data
  • Qdrant: A vector database essential for semantic search and RAG applications
  • Redis: Used for caching and performance optimization
  • Langflow: Facilitates workflow management for language models
  • Linkwarden: A bookmark and web content manager for research and reference
  • N8N: My chosen workflow automation platform
  • Unstructured: For extracting content from a variety of file formats

The implementation also includes monitoring (Glances) and backup (Duplicati) to ensure a robust and maintainable system.

APIs Beyond LLMs

I leverage specialized APIs alongside LLMs to enhance specific tasks:

  • Tavily: This search API provides relevant, up-to-date information, making it ideal for RAG applications and ensuring LLMs have access to current knowledge.
  • Sonar by Perplexity: Delivers powerful search capabilities with built-in summarization and information synthesis, particularly effective for research and gathering comprehensive information on specific topics.

Final Thoughts

This stack represents what works for me right now, but I expect it to continue evolving as new tools emerge and existing ones improve. The AI landscape of 2025 is incredibly dynamic, with new capabilities appearing almost weekly.

If you're building your own AI stack, I encourage you to experiment and find the combination of tools that best suits your specific needs and workflow. What works for me might not be ideal for you, and that's perfectly fine.

I'll continue updating my GitHub repository as my stack evolves, so feel free to check back periodically if you're interested in tracking changes over time.

What does your AI stack look like in 2025? I'd love to hear about the tools and approaches that are working well for you.

Resource Directory

Navigate this section: LLM APIs | Frontend Tools | Speech & Voice | Storage & Databases | Agents & Workflows | Media Tools | Development | Infrastructure | Search APIs | My Projects

LLM APIs & Models

ToolDescriptionLink
CohereLLM API specialized for instructional text tasksWebsite
Google Flash 2.0Fast inference LLM with large context windowWebsite
OpenAIProvider of Sonnet 3.7 and other LLMsWebsite
OpenRouterConsolidated access to multiple LLM APIsWebsite
QwenLLM models optimized for coding tasksWebsite

Frontend & Interface Tools

ToolDescriptionLink
LangflowVisual interface for building LLM workflowsWebsite
Open InterpreterTerminal-based LLM interfaceWebsite
Open Web UIFrontend for interacting with LLMsWebsite

Speech & Voice Tools

ToolDescriptionLink
Futo KeyboardOpen source Android keyboard with speech-to-textWebsite
Whisper AISpeech-to-text modelGitHub

Storage & Databases

ToolDescriptionLink
MongoDBNoSQL database for AI outputsWebsite
PostgreSQLRelational database with PGVector extensionWebsite
QdrantVector database for semantic searchWebsite
RedisIn-memory data store for cachingWebsite

Agents & Workflows

ToolDescriptionLink
LinkwardenBookmark and web content managerWebsite
N8NWorkflow automation platformWebsite
UnstructuredTool for extracting content from various file formatsWebsite

Generative Media Tools

ToolDescriptionLink
Leonardo AIText-to-image generation platformWebsite
Runway MLPlatform for text-to-video and animationWebsite

Development Tools

ToolDescriptionLink
AiderAI coding assistant for precise context specificationWebsite
OpenSUSE LinuxLinux distributionWebsite
WindsurfAI-powered IDEWebsite

Infrastructure & Deployment

ToolDescriptionLink
DuplicatiBackup solution for AI stackWebsite
GlancesSystem monitoring toolWebsite

Search & Knowledge APIs

ToolDescriptionLink
Sonar by PerplexitySearch API with summarizationWebsite
TavilySearch API for RAG applicationsWebsite

My Projects

ProjectDescriptionLink
AI Assistants LibraryCollection of AI assistants and system promptsWebsite
Agentic-Context-Development-Interview-DemoProject for developing contextual data through interviewsGitHub
My AI Stack RepositoryGitHub repository with my complete AI stackGitHub
My-LLM-Context-Repo-PublicPublic repository for LLM context dataGitHub
Personal-RAG-Agent-WorkflowWorkflow for personal RAG implementationGitHub

Docker Compose (Model)

services:
  # OpenWebUI and its dependencies
  openwebui:
    image: ghcr.io/open-webui/open-webui:latest
    container_name: openwebui
    hostname: openwebui
    restart: unless-stopped
    depends_on:
      - postgres
      - qdrant
    environment:
      - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/openwebui
      - QDRANT_URI=http://qdrant:${QDRANT_PORT:-6333}
      - VECTOR_DB=${VECTOR_DB:-qdrant}
      - QDRANT_API_KEY=${QDRANT_API_KEY:-$QDRANT_KEY}
      - PORT=${OPENWEBUI_PORT:-8080}
      # RAG Configuration
      - RAG_EMBEDDING_ENGINE=${RAG_EMBEDDING_ENGINE:-openai}
      - CHUNK_SIZE=${CHUNK_SIZE:-400}
      - CHUNK_OVERLAP=${CHUNK_OVERLAP:-50}
      - RAG_TOP_K=${RAG_TOP_K:-4}
      - RAG_RELEVANCE_THRESHOLD=${RAG_RELEVANCE_THRESHOLD:-0.15}
      - RAG_TEXT_SPLITTER=${RAG_TEXT_SPLITTER:-character}
      - ENABLE_RAG_HYBRID_SEARCH=${ENABLE_RAG_HYBRID_SEARCH:-True}
      - CONTENT_EXTRACTION_ENGINE=${CONTENT_EXTRACTION_ENGINE:-unstructured}
      - UNSTRUCTURED_API_URL=http://unstructured:8000
      - UNSTRUCTURED_API_KEY=${UNSTRUCTURED_API_KEY:-$UNSTRUCTURED_KEY}
      - PDF_EXTRACT_IMAGES=${PDF_EXTRACT_IMAGES:-True}
      - RAG_FILE_MAX_SIZE=${RAG_FILE_MAX_SIZE:-10}
      - RAG_FILE_MAX_COUNT=${RAG_FILE_MAX_COUNT:-5}
      # RAG OpenAI Configuration (commented out until values are provided)
      # - RAG_OPENAI_API_KEY=${RAG_OPENAI_API_KEY}
      # - RAG_OPENAI_API_BASE_URL=${RAG_OPENAI_API_BASE_URL}
      # Enable Redis for caching
      - REDIS_URL=redis://default:${REDIS_PASSWORD:-$REDIS_PASS}@redis:6379
      # API Keys for various LLM providers
      - OPENAI_API_KEY=${OPENAI_API_KEY:-$OPENAI_KEY}
      - ANTHROPIC_API_KEY=${ANTHROPIC_KEY:-$ANTHROPIC_KEY}
      - GOOGLE_API_KEY=${GOOGLE_API_KEY:-$GOOGLE_KEY}
      - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY:-$DEEPSEEK_KEY}
      - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-$OPENROUTER_KEY}
      - MISTRAL_API_KEY=${MISTRAL_API_KEY:-$MISTRAL_KEY}
      - PERPLEXITY_API_KEY=${PERPLEXITY_API_KEY:-$PERPLEXITY_KEY}
      - COHERE_API_KEY=${COHERE_API_KEY:-$COHERE_KEY}
      # Google Drive authentication
      - GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID
      - GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET
      - GOOGLE_REDIRECT_URI=$GOOGLE_REDIRECT_URI
      - GOOGLE_AUTH_ENABLED=true
      - ENABLE_GOOGLE_DRIVE_INTEGRATION=true
      # Performance optimization settings
      - ENABLE_WEBSOCKET_SUPPORT=true
      - WEBSOCKET_MANAGER=redis
      - WEBSOCKET_REDIS_URL=redis://default:${REDIS_PASSWORD:-$REDIS_PASS}@redis:6379
      - DATABASE_POOL_SIZE=20
      - DATABASE_POOL_MAX_OVERFLOW=10
      - DATABASE_POOL_TIMEOUT=30
      - DATABASE_POOL_RECYCLE=1800
      # Disable autocomplete
      - ENABLE_AUTOCOMPLETE_GENERATION=false
      # Admin credentials
      - DEFAULT_USER_EMAIL=$DEFAULT_USER_EMAIL
      - DEFAULT_USER_PASSWORD=$DEFAULT_USER_PASSWORD
      - DEFAULT_USER_FIRST_NAME=$DEFAULT_USER_FIRST_NAME
      - DEFAULT_USER_LAST_NAME=$DEFAULT_USER_LAST_NAME
      - DEFAULT_USER_ROLE=admin
      # CORS configuration
      - CORS_ALLOW_ORIGIN=$CORS_ALLOW_ORIGIN
    volumes:
      - openwebui_data:/app/backend/data
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
  postgres:
    image: postgres:15-alpine
    container_name: postgres
    hostname: postgres
    restart: unless-stopped
    command: postgres -c shared_buffers=256MB -c work_mem=16MB -c maintenance_work_mem=128MB -c effective_cache_size=512MB -c max_connections=100
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=$POSTGRES_PASSWORD
      - POSTGRES_DB=postgres
      - POSTGRES_MULTIPLE_DATABASES=${LANGFLOW_DB:-langflow},${LINKWARDEN_DB:-linkwarden},${N8N_DB:-n8n}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/postgres-init:/docker-entrypoint-initdb.d
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
  qdrant:
    image: qdrant/qdrant:latest
    container_name: qdrant
    hostname: qdrant
    environment:
      - QDRANT__SERVICE__API_KEY=${QDRANT_API_KEY:-$QDRANT_KEY}
      - QDRANT__SERVICE__ENABLE_API_KEY_AUTHORIZATION=true
    restart: unless-stopped
    volumes:
      - qdrant_data:/qdrant/storage
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:6333/readiness"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  qdrant-dashboard-proxy:
    image: nginx:alpine
    container_name: qdrant-dashboard-proxy
    hostname: qdrantdashboard
    restart: unless-stopped
    depends_on:
      - qdrant
    volumes:
      - ./config/nginx/qdrant-dashboard.conf:/etc/nginx/conf.d/default.conf:ro
    ports:
      - "8090:80"
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
  redis:
    image: redis:alpine
    container_name: redis
    hostname: redis
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-$REDIS_PASS} --maxmemory 384mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s
  langflow:
    image: langflowai/langflow:latest
    container_name: langflow
    hostname: langflow
    restart: unless-stopped
    depends_on:
      - postgres
      - redis
    environment:
      - LANGFLOW_DATABASE_URL=postgresql://postgres:$POSTGRES_PASSWORD@postgres:5432/${LANGFLOW_DB:-langflow}
      - LANGFLOW_REDIS_URL=redis://default:${REDIS_PASSWORD:-$REDIS_PASS}@redis:6379
      - LANGFLOW_SUPERUSER=$LANGFLOW_SUPERUSER
      - LANGFLOW_SUPERUSER_PASSWORD=$LANGFLOW_SUPERUSER_PASSWORD
      - LANGFLOW_AUTO_LOGIN=False
      - AUTH_ENABLED=true
      # CORS configuration
      - LANGFLOW_CORS_ORIGINS=$CORS_ALLOW_ORIGIN
      # Unstructured integration
      - LANGFLOW_UNSTRUCTURED_API_URL=http://unstructured:8000
      - LANGFLOW_UNSTRUCTURED_API_KEY=${UNSTRUCTURED_API_KEY:-$UNSTRUCTURED_KEY}
    volumes:
      - langflow_data:/app/backend/langflow_data
    ports:
      - "${LANGFLOW_PORT:-7860}:7860"
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7860/health"]
      interval: 30s
      timeout: 10s
      retries: 3
  linkwarden:
    image: ghcr.io/linkwarden/linkwarden:latest
    container_name: linkwarden
    hostname: linkwarden
    restart: unless-stopped
    ports:
      - "${LINKWARDEN_PORT:-3000}:3000"
    environment:
      - DATABASE_URL=postgresql://postgres:$POSTGRES_PASSWORD@postgres:5432/${LINKWARDEN_DB:-linkwarden}
      - NEXTAUTH_SECRET=$NEXTAUTH_SECRET
      - NEXTAUTH_URL=$NEXTAUTH_URL
      - REDIS_URL=redis://default:${REDIS_PASSWORD:-$REDIS_PASS}@redis:6379
      # CORS configuration
      - NEXT_PUBLIC_CORS_ALLOWED_ORIGINS=$NEXT_PUBLIC_CORS_ALLOWED_ORIGINS
      - NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
    volumes:
      - linkwarden_data:/data
    networks:
      - dsrholdingsai
    depends_on:
      - postgres
      - redis
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: pgadmin
    hostname: pgadmin
    restart: unless-stopped
    ports:
      - "${PGADMIN_PORT:-5050}:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=${PGADMIN_EMAIL:[email protected]}
      - PGADMIN_DEFAULT_PASSWORD=$PGADMIN_PASSWORD
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    networks:
      - dsrholdingsai
    depends_on:
      - postgres
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    hostname: n8n
    restart: unless-stopped
    ports:
      - "${N8N_PORT:-5678}:5678"
    environment:
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - N8N_HOST=${N8N_HOST:-n8n.example.com}
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${N8N_DB:-n8n}
      - DB_POSTGRESDB_USER=postgres
      - DB_POSTGRESDB_PASSWORD=$POSTGRES_PASSWORD
      - N8N_EMAIL_MODE=smtp
      - N8N_SMTP_HOST=${SMTP_HOST:-smtp.example.com}
      - N8N_SMTP_PORT=${SMTP_PORT:-587}
      - N8N_SMTP_USER=${SMTP_USER:[email protected]}
      - N8N_SMTP_PASS=${SMTP_PASS:-$SMTP_PASS}
      - N8N_SMTP_SENDER=${SMTP_SENDER:[email protected]}
      - N8N_BASIC_AUTH_ACTIVE=false
      - N8N_USER_MANAGEMENT_DISABLED=false
      - N8N_DEFAULT_USER_EMAIL=${N8N_USER_EMAIL:[email protected]}
      - N8N_DEFAULT_USER_PASSWORD=${N8N_PASSWORD:-$N8N_PASSWORD}
      - N8N_DEFAULT_USER_FIRSTNAME=${N8N_USER_FIRSTNAME:-FirstName}
      - N8N_DEFAULT_USER_LASTNAME=${N8N_USER_LASTNAME:-LastName}
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD:-$REDIS_PASS}
      - N8N_WEBHOOK_URL=${N8N_WEBHOOK_URL:-https://n8n.example.com}
      - N8N_RUNNERS_ENABLED=${N8N_RUNNERS_ENABLED:-true}
      # CORS configuration
      - N8N_CORS_ALLOW_ORIGIN=$CORS_ALLOW_ORIGIN
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - dsrholdingsai
    depends_on:
      - postgres
      - redis
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5678"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  glances:
    image: nicolargo/glances:latest
    container_name: glances
    hostname: glances
    restart: unless-stopped
    ports:
      - "${GLANCES_PORT:-61208}:61208"
    environment:
      - GLANCES_OPT=-w
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /:/rootfs:ro
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
    pid: host
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:61208"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  backup:
    image: linuxserver/duplicati:latest
    container_name: backup
    hostname: backup
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Jerusalem
      # Backup configuration for incremental backups every 3rd day
      - CLI_ARGS=--backup-name="AI Stack Incremental Backup" --dbpath=/config/AISTACK.sqlite --backup-retention-policy=30D:1W,1W:1M --run-script-before=/config/scripts/db-dump.sh --run-script-after=/config/scripts/backup-notification.sh
      # Database-specific backup job
      - DB_BACKUP_ARGS=--backup-name="Database Backup" --dbpath=/config/DB_BACKUP.sqlite --backup-retention-policy=7D:1D,30D:1W --include-folder=/source/db_dumps
      # B2 Credentials
      - B2_ACCOUNT_ID=${B2_APP_KEY_ID:-$B2_ACCOUNT_ID}
      - B2_ACCOUNT_KEY=${B2_APP_KEY:-$B2_ACCOUNT_KEY}
      - B2_BUCKET_NAME=${B2_BUCKET:-aistack}
      # Default schedule: every 3rd day at 3 AM for full backup, daily at 1 AM for DB backup
      - CRON_EXPRESSION=0 3 */3 * *
      - DB_CRON_EXPRESSION=0 1 * * *
    ports:
      - "${BACKUP_PORT:-8200}:8200"
    volumes:
      - backup_config:/config
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./scripts/backup:/config/scripts:ro
      - db_dumps:/source/db_dumps
      - openwebui_data:/source/openwebui_data:ro
      - postgres_data:/source/postgres_data:ro
      - qdrant_data:/source/qdrant_data:ro
      - langflow_data:/source/langflow_data:ro
      - linkwarden_data:/source/linkwarden_data:ro
      - pgadmin_data:/source/pgadmin_data:ro
      - n8n_data:/source/n8n_data:ro
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8200"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  qdrant-backup:
    image: linuxserver/duplicati:latest
    container_name: qdrant-backup
    hostname: qdrant-backup
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Jerusalem
      # Qdrant-specific backup job
      - CLI_ARGS=--backup-name="Qdrant Vector DB Backup" --dbpath=/config/QDRANT_BACKUP.sqlite --backup-retention-policy=30D:1W,1W:1M --run-script-before=/config/scripts/qdrant-backup.sh --run-script-after=/config/scripts/qdrant-backup-notification.sh
      # B2 Credentials (same account but different bucket)
      - B2_ACCOUNT_ID=${B2_APP_KEY_ID:-$B2_ACCOUNT_ID}
      - B2_ACCOUNT_KEY=${B2_APP_KEY:-$B2_ACCOUNT_KEY}
      - B2_BUCKET_NAME=qdrantbackups
      # Schedule: daily at 2 AM for Qdrant backup
      - CRON_EXPRESSION=0 2 * * *
    ports:
      - "${QDRANT_BACKUP_PORT:-8201}:8200"
    volumes:
      - qdrant_backup_config:/config
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./scripts/backup:/config/scripts:ro
      - qdrant_backups:/source/qdrant_backups
      - qdrant_data:/source/qdrant_data:ro
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8200"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  backup-dashboard:
    image: nginx:alpine
    container_name: backup-dashboard
    hostname: backup-dashboard.example.com
    restart: unless-stopped
    volumes:
      - ./config/nginx/backup-dashboard.conf:/etc/nginx/conf.d/default.conf:ro
      - ./config/nginx/html:/usr/share/nginx/html:ro
    ports:
      - "${BACKUP_DASHBOARD_PORT:-8202}:80"
    depends_on:
      - backup
      - qdrant-backup
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  unstructured:
    image: quay.io/unstructured-io/unstructured-api:latest
    container_name: unstructured
    hostname: unstructured
    restart: unless-stopped
    ports:
      - "${UNSTRUCTURED_PORT:-8000}:8000"
    environment:
      - UNSTRUCTURED_API_KEY=${UNSTRUCTURED_API_KEY:-$UNSTRUCTURED_KEY}
      - METRICS_ENABLED=true
      - LOG_LEVEL=INFO
      - MAX_DOCUMENT_SIZE=20971520
      - CORS_ORIGINS=$CORS_ALLOW_ORIGIN
    volumes:
      - unstructured_data:/home/unstructured/.cache
    networks:
      - dsrholdingsai
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
        reservations:
          cpus: '0.5'
          memory: 2G
networks:
  dsrholdingsai:
    external: true
    name: dsrholdingsai_network
volumes:
  openwebui_data:
  postgres_data:
  qdrant_data:
  langflow_data:
  linkwarden_data:
  pgadmin_data:
  n8n_data:
  redis_data:
  backup_config:
  qdrant_backup_config:
  db_dumps:
    driver: local
  qdrant_backups:
    driver: local
  unstructured_data: