diff --git a/.github/workflows/release-containers.yml b/.github/workflows/release-containers.yml index ea0162ea..2b6b410b 100644 --- a/.github/workflows/release-containers.yml +++ b/.github/workflows/release-containers.yml @@ -100,11 +100,11 @@ jobs: id: build run: | make docker VERSION=${{ needs.set-version.outputs.version }} ARCH=${{ matrix.arch }} + make docker-base VERSION=${{ needs.set-version.outputs.version }} ARCH=${{ matrix.arch }} make publish-ecr VERSION=${{ needs.set-version.outputs.version }} ARCH=${{ matrix.arch }} - name: Push GHCR if: github.event_name != 'push' || startsWith(github.ref, 'refs/tags/') - working-directory: ./docker run: | make publish-ghcr VERSION=${{ needs.set-version.outputs.version }} ARCH=${{ matrix.arch }} @@ -144,12 +144,16 @@ jobs: - name: Push manifest to ECR working-directory: ./docker - run: make ecr-manifest VERSION=${{ needs.set-version.outputs.version }} + run: | + make ecr-manifest VERSION=${{ needs.set-version.outputs.version }} + make ecr-manifest INSTALL_TOOLKITS=false VERSION=${{ needs.set-version.outputs.version }} - name: Push manifest to GHCR if: github.event_name != 'push' || startsWith(github.ref, 'refs/tags/') working-directory: ./docker - run: make ghcr-manifest VERSION=${{ needs.set-version.outputs.version }} + run: | + make ghcr-manifest VERSION=${{ needs.set-version.outputs.version }} + make ghcr-manifest INSTALL_TOOLKITS=false VERSION=${{ needs.set-version.outputs.version }} # Currently broken: workflow deploy requires all versions # deploy: diff --git a/Makefile b/Makefile index 3cd50b90..83eb8876 100644 --- a/Makefile +++ b/Makefile @@ -91,15 +91,35 @@ build-and-publish: build publish ## Build and publish. docker: ## Build and run the Docker container @echo "šŸš€ Building arcade and toolkit wheels..." @make full-dist - @echo "Writing extras [fastapi, evals] to requirements.txt" - @cd arcade && poetry export --extras "fastapi evals" --output ../dist/requirements.txt + @echo "Writing extras requirements.txt" + @cd arcade && poetry export --extras "fastapi" --output ../dist/requirements.txt @echo "šŸš€ Building Docker image" @cd docker && make docker-build @cd docker && make docker-run +.PHONY: docker-base +docker-base: ## Build and run the Docker container + @echo "šŸš€ Building arcade and toolkit wheels..." + @make full-dist + @echo "Writing extras requirements.txt" + @cd arcade && poetry export --extras "fastapi" --output ../dist/requirements.txt + @echo "šŸš€ Building Docker image" + @cd docker && INSTALL_TOOLKITS=false make docker-build + @cd docker && INSTALL_TOOLKITS=false make docker-run + .PHONY: publish-ecr publish-ecr: ## Publish to the ECR - @cd docker && make publish-ecr + # Publish the base image - /arcadeai/worker-base + @cd docker && INSTALL_TOOLKITS=false make publish-ecr + # Publish the image with toolkits - /arcadeai/worker + @cd docker && INSTALL_TOOLKITS=true make publish-ecr + +.PHONY: publish-ghcr +publish-ghcr: ## Publish to the GHCR + # Publish the base image - ghcr.io/arcadeai/worker-base + @cd docker && INSTALL_TOOLKITS=false make publish-ghcr + # Publish the image with toolkits - ghcr.io/arcadeai/worker + @cd docker && INSTALL_TOOLKITS=true make publish-ghcr .PHONY: full-dist full-dist: clean-dist ## Build all projects and copy wheels to ./dist @@ -121,19 +141,6 @@ full-dist: clean-dist ## Build all projects and copy wheels to ./dist @echo "Reset version to default (0.1.0)" @make unset-version - @echo "šŸ› ļø Building all projects and copying wheels to ./dist" - # Build and copy wheels for each toolkit - # @for toolkit_dir in toolkits/*; do \ - # if [ -d "$$toolkit_dir" ]; then \ - # toolkit_name=$$(basename "$$toolkit_dir"); \ - # echo "Building $$toolkit_name project..."; \ - # poetry build; \ - # cp dist/*.whl ../../dist/toolkits; \ - # cd -; \ - # fi; \ - # done - - @echo "āœ… All toolkits built and wheels copied to ./dist" .PHONY: clean-dist clean-dist: ## Clean all built distributions diff --git a/docker/Dockerfile b/docker/Dockerfile index 28f7d194..05bce16a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,25 +1,20 @@ -# Use a lightweight Python image FROM python:3.10-slim # Define build arguments with default values ARG PORT=8001 ARG HOST=0.0.0.0 -ARG VERSION=0.1.0 +ARG VERSION=${VERSION:-0.1.0} +ARG INSTALL_TOOLKITS=true # Set environment variables using the build arguments ENV PORT=${PORT} ENV HOST=${HOST} -ENV VERSION=${VERSION} ENV OTEL_ENABLE=false ENV ARCADE_WORK_DIR=/app -ENV TOOLKITS="arcade-code-sandbox,arcade-github,arcade-google,arcade-linkedin,arcade-math,arcade-search,arcade-slack,arcade-spotify,arcade-web,arcade-x,arcade-zoom" # Install system dependencies RUN apt-get update && apt-get install -y \ - apt-utils \ - build-essential \ libssl-dev \ - libffi-dev \ python3-dev \ curl \ && apt-get clean \ @@ -30,6 +25,12 @@ WORKDIR /app/arcade # Copy the parent directory contents into the container COPY ./dist ./arcade /app/arcade/ +# Copy the toolkits.txt file into the container +COPY ./docker/toolkits.txt /app/arcade/ + +# Expose the port +EXPOSE $PORT + # List files for debugging purposes RUN ls -la /app/arcade/ @@ -49,8 +50,10 @@ RUN if [ ! "$(echo ${VERSION} | grep -E '\.dev0$')" ]; then \ pip install .; \ fi -# Expose the port -EXPOSE $PORT +# Conditionally install toolkits.txt dependencies +RUN if [ "$INSTALL_TOOLKITS" = "true" ] ; then \ + python -m pip install -r ./toolkits.txt ; \ + fi # Run the arcade workerup (hidden cli command) COPY docker/start.sh /app/start.sh diff --git a/docker/Makefile b/docker/Makefile index 5668f374..29067854 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -2,24 +2,35 @@ VENDOR ?= ArcadeAI PROJECT ?= ArcadeAI SOURCE ?= https://github.com/ArcadeAI/arcade-ai LICENSE ?= MIT -DESCRIPTION ?= "Arcade AI for LLM Tool Serving" +DESCRIPTION ?= "Arcade Worker for LLM Tool Serving" REPOSITORY ?= arcadeai/worker ECR_ENDPOINT ?= 471112909428.dkr.ecr.us-east-1.amazonaws.com -ARCH ?= arm64 +ARCH ?= $(shell uname -m) VERSION ?= 0.1.0.dev0 COMMIT ?= $(shell git describe --dirty --always --abbrev=15) BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") IMAGE_NAME ?= worker PORT ?= 8002 +INSTALL_TOOLKITS ?= true +# If INSTALL_TOOLKITS is true, we are building the worker image with all toolkits +# Otherwise, we are building the worker image with no toolkits +ifeq ($(INSTALL_TOOLKITS), true) + REPOSITORY := $(REPOSITORY) +else + REPOSITORY := $(REPOSITORY)-base +endif + +VERSION_TAG := $(VERSION)-$(ARCH) .PHONY: docker-build docker-build: ## Build the Docker container - @echo "šŸ› ļø Building Docker image ($(VERSION)).." + @echo "šŸ› ļø Building Docker image ($(VERSION_TAG)).." @echo "- Commit: $(COMMIT)" @echo "- Build Date: $(BUILD_DATE)" - @docker build --build-arg PORT=$(PORT) -f Dockerfile -t $(REPOSITORY):$(VERSION) .. \ + @docker build --build-arg PORT=$(PORT) -f Dockerfile -t $(REPOSITORY):$(VERSION_TAG) \ + --build-arg INSTALL_TOOLKITS=$(INSTALL_TOOLKITS) \ --build-arg PORT=$(PORT) \ --build-arg VERSION="$(VERSION)" \ --build-arg COMMIT="$(COMMIT)" \ @@ -27,14 +38,16 @@ docker-build: ## Build the Docker container --label=org.opencontainers.image.vendor="$(VENDOR)" \ --label=org.opencontainers.image.title="$(PROJECT)" \ --label=org.opencontainers.image.revision="$(COMMIT)" \ - --label=org.opencontainers.image.version="$(VERSION)" \ + --label=org.opencontainers.image.version="$(VERSION_TAG)" \ --label=org.opencontainers.image.created="$(BUILD_DATE)" \ --label=org.opencontainers.image.source="$(SOURCE)" \ --label=org.opencontainers.image.licenses="$(LICENSE)" \ - --label=org.opencontainers.image.description=$(DESCRIPTION) + --label=org.opencontainers.image.description=$(DESCRIPTION) \ + .. + ecr-manifest: ## Make a manifest file for the image - @echo "šŸ› ļø Build manifest file ($(VERSION)).." + @echo "šŸ› ļø Build manifest file for $(REPOSITORY):$(VERSION).." @echo "- Commit: $(COMMIT)" @echo "- Build Date: $(BUILD_DATE)" @export DOCKER_CLI_EXPERIMENTAL=enabled @@ -56,7 +69,7 @@ ecr-manifest: ## Make a manifest file for the image @docker manifest push $(ECR_ENDPOINT)/$(REPOSITORY):latest ghcr-manifest: ## Make a manifest file for the image - @echo "šŸ› ļø Build manifest file ($(VERSION)).." + @echo "šŸ› ļø Build manifest file for $(REPOSITORY):$(VERSION).." @echo "- Commit: $(COMMIT)" @echo "- Build Date: $(BUILD_DATE)" @export DOCKER_CLI_EXPERIMENTAL=enabled @@ -80,17 +93,16 @@ ghcr-manifest: ## Make a manifest file for the image .PHONY: docker-run docker-run: ## Run the Docker container @echo "\nšŸš€ Run the container with the following ..." - @echo ">>> docker run -d -p $(PORT):$(PORT) $(REPOSITORY):$(VERSION)" - + @echo ">>> docker run -d -p $(PORT):$(PORT) $(REPOSITORY):$(VERSION_TAG)" .PHONY: publish-ecr publish-ecr: @echo "🚚 Pushing the Agent image to ECR.." - @docker tag $(REPOSITORY):$(VERSION) $(ECR_ENDPOINT)/$(REPOSITORY):$(VERSION)-$(ARCH) - @echo "- pushing $(ECR_ENDPOINT)/$(REPOSITORY):$(VERSION)-$(ARCH)" - @docker push $(ECR_ENDPOINT)/$(REPOSITORY):$(VERSION)-$(ARCH) - @echo $(VERSION) | grep -q $(RC_PART) || { \ - docker tag $(REPOSITORY):$(VERSION) $(ECR_ENDPOINT)/$(REPOSITORY):latest-$(ARCH); \ + @docker tag $(REPOSITORY):$(VERSION_TAG) $(ECR_ENDPOINT)/$(REPOSITORY):$(VERSION_TAG) + @echo "- pushing $(ECR_ENDPOINT)/$(REPOSITORY):$(VERSION_TAG)" + @docker push $(ECR_ENDPOINT)/$(REPOSITORY):$(VERSION_TAG) + @echo $(VERSION_TAG) | grep -q $(RC_PART) || { \ + docker tag $(REPOSITORY):$(VERSION_TAG) $(ECR_ENDPOINT)/$(REPOSITORY):latest-$(ARCH); \ echo "- pushing $(ECR_ENDPOINT)/$(REPOSITORY):latest-$(ARCH)"; \ docker push $(ECR_ENDPOINT)/$(REPOSITORY):latest-$(ARCH); \ } @@ -98,20 +110,30 @@ publish-ecr: .PHONY: publish-ghcr publish-ghcr: @echo "🚚 Pushing the Agent image to GHCR.." - @docker tag $(REPOSITORY):$(VERSION) ghcr.io/$(REPOSITORY):$(VERSION)-$(ARCH) - @echo "- pushing ghcr.io/$(REPOSITORY):$(VERSION)-$(ARCH)" - @docker push ghcr.io/$(REPOSITORY):$(VERSION)-$(ARCH) + @docker tag $(REPOSITORY):$(VERSION_TAG) ghcr.io/$(REPOSITORY):$(VERSION_TAG) + @echo "- pushing ghcr.io/$(REPOSITORY):$(VERSION_TAG)" + @docker push ghcr.io/$(REPOSITORY):$(VERSION_TAG) @echo $(VERSION) | grep -q $(RC_PART) || { \ - docker tag $(REPOSITORY):$(VERSION) ghcr.io/$(REPOSITORY):latest-$(ARCH); \ + docker tag $(REPOSITORY):$(VERSION_TAG) ghcr.io/$(REPOSITORY):latest-$(ARCH); \ echo "- pushing ghcr.io/$(REPOSITORY):latest-$(ARCH)"; \ docker push ghcr.io/$(REPOSITORY):latest-$(ARCH); \ } .PHONY: ecr-login -ecr-login: # Login to ECR +ecr-login: ## Login to ECR @aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $(ECR_ENDPOINT) + +.PHONY: gh-login +gh-login: ## Login to GHCR + @echo "🚚 Logging in to GHCR..." + @if [ -z "$(GHCR_PAT)" ]; then \ + echo "Error: GHCR_PAT environment variable is not set"; \ + exit 1; \ + fi + @echo $(GHCR_PAT) | docker login ghcr.io -u arcadeai --password-stdin + .PHONY: help help: @echo "šŸ› ļø Worker Docker Commands:\n" diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..f9e659fd --- /dev/null +++ b/docker/README.md @@ -0,0 +1,115 @@ +# Arcade Docker Compose Guide + +This guide provides detailed instructions on how to set up and run Arcade using Docker Compose. + +## Prerequisites + +- **Docker** installed on your system. [Install Docker](https://docs.docker.com/get-docker/) +- **Docker Compose** installed. It comes bundled with Docker Desktop on Windows and macOS. For Linux, follow the [Docker Compose installation guide](https://docs.docker.com/compose/install/). + +## Getting Started + +### 1. Clone the Repository + +Begin by cloning the Arcade repository: + +```bash +git clone https://github.com/ArcadeAI/arcade-ai.git +``` + +Change to the `docker` directory: + +```bash +cd arcade-ai/docker +``` + +### 2. Copy and Configure Environment Variables + +Copy the example environment file to `.env`: + +```bash +cp env.example .env +``` + +Open the `.env` file in your preferred text editor and fill in the required values. At a minimum, you **must** provide the `OPENAI_API_KEY`: + +```env:.env +### LLM ### + +OPENAI_API_KEY=your_openai_api_key_here +``` + +If you plan to use other Large Language Model (LLM) providers, add their API keys as well: + +```env:.env +ANTHROPIC_API_KEY=your_anthropic_api_key_here +``` + +### 3. Run Docker Compose + +Start the Arcade services using Docker Compose: + +```bash +docker compose up -p arcade +``` + +This command will build and start all the services defined in the `docker-compose.yml` file and make their ports available to your host machine. + +### 4. Verify the Engine is Running + +In a separate terminal window, check if the engine is running: + +```bash +curl http://localhost:9099/v1/health +``` + +You should receive a response indicating that the engine is healthy: + +```json +{ "status": "healthy" } +``` + +You can also view the swagger docs at `http://localhost:9099/swagger/index.html` + +## Adding Authentication Providers + +Arcade supports various authentication providers. To add an auth provider, follow these steps: + +### 1. Enable the Auth Provider in the Configuration + +Edit the `docker.engine.yaml` file to enable the desired auth provider. For example, to enable Google authentication, modify the file as follows: + +```yaml:docker.engine.yaml +auth: + providers: + - id: google + enabled: true # Change from false to true +``` + +### 2. Add Client ID and Secret to the `.env` File + +Obtain the client ID and client secret from your auth provider and add them to the `.env` file: + +```env:.env +GOOGLE_CLIENT_ID="your_google_client_id" +GOOGLE_CLIENT_SECRET="your_google_client_secret" +``` + +Repeat this step for any other auth providers you wish to enable. + +### 3. Restart the Docker Compose Services + +After making changes to the configuration, restart the services: + +```bash +docker compose down +docker compose up -p arcade +``` + +## Troubleshooting + +- **Engine Health Check Fails**: Ensure that all environment variables are correctly set in the `.env` file and that the services have started without errors. +- **Port Conflicts**: If the default ports are already in use, modify the ports in the `docker-compose.yml` file. +- **Authentication Errors**: Double-check the client IDs and secrets provided for auth providers. + +NOTE: `arcade login` will not work within a docker container, you must copy your credentials into the container if you would like to use it. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..08648cd4 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,62 @@ +version: '3.8' + +services: + engine: + image: ghcr.io/arcadeai/engine:latest + container_name: arcade-engine + volumes: + - ./docker.engine.yaml:/bin/engine.yaml + - ./.env:/bin/.env + - ./arcade-engine.sqlite3:/app/arcade-engine.sqlite3 + ports: + - "9099:9099" + depends_on: + redis: + condition: service_healthy + worker: + condition: service_started + networks: + arcade-network: + command: /bin/arcade-engine --config /bin/engine.yaml --env /bin/.env --migrate + + worker: +# image: ghcr.io/arcadeai/arcade-ai:latest + image: arcade-worker:0.1.0 + container_name: arcade-worker + ports: + - "8002:8002" + networks: + arcade-network: + + redis: + image: redis/redis-stack:latest + container_name: arcade-redis + ports: + - "6379:6379" + - "8004:8002" + depends_on: + worker: + condition: service_started + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 3s + timeout: 3s + retries: 5 + networks: + arcade-network: + + nginx: + image: nginx:stable-alpine + container_name: arcade-nginx + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - engine + networks: + - arcade-network + +networks: + arcade-network: + driver: bridge diff --git a/docker/docker.engine.yaml b/docker/docker.engine.yaml new file mode 100644 index 00000000..b34817d1 --- /dev/null +++ b/docker/docker.engine.yaml @@ -0,0 +1,173 @@ +telemetry: + environment: local + version: ${env:VERSION} + logging: + level: debug # debug, info, warn, error + encoding: console + +api: + development: true + host: 0.0.0.0 + port: 9099 + analytics: + enabled: false + # Uncomment to enable rate limiter + # rate_limit: + # redis: + # addr: "localhost:6379" + # password: "" + # db: 2 + # time_unit: m + # limit: 2 + # write_timeout: 10 + # read_timeout: 10 + +llm: + models: + - id: oai + openai: + api_key: ${env:OPENAI_API_KEY} + # - id: anthropic + # anthropic: + # api_key: ${env:ANTHROPIC_API_KEY} + # # model: "claude-3-5-sonnet-20240620" + #- id: ollama + # openai: + # base_url: "http://localhost:11434/v1" + # api_key: "ollama" + +tools: + directors: + - id: default + enabled: true + workers: + - id: "localworker" + enabled: true + http: + uri: ${env:WORKER_URL} + timeout: 30 + retry: 3 + secret: ${env:ARCADE_WORKER_SECRET} # If not set, defaults to "dev" in development mode only + # Uncomment mock and comment http to start engine without live worker + # mock: + # enabled: true + +security: + root_keys: + - id: key1 + default: true + value: ${env:ROOT_KEY_1} + # - id: key2 + # value: ${env:ROOT_KEY_2} + +storage: + # postgres: + # user: postgres + # password: 123456 + # host: localhost + # port: 5432 + # db: arcade_engine + # sslmode: disable + sqlite: # Default for local development + connection_string: "/app/arcade-engine.sqlite3" + +cache: + api_key_ttl: "10s" + redis: + addr: ${env:REDIS_HOST}:${env:REDIS_PORT} + password: ${env:REDIS_PASSWORD} + +auth: + token_store: + redis: + addr: ${env:REDIS_HOST}:${env:REDIS_PORT} + password: ${env:REDIS_PASSWORD} + + providers: + - id: default-atlassian + description: "The default Atlassian provider" + enabled: false + type: oauth2 + provider_id: atlassian + client_id: ${env:ATLASSIAN_CLIENT_ID} + client_secret: ${env:ATLASSIAN_CLIENT_SECRET} + + - id: default-discord + description: "The default Discord provider" + enabled: false + type: oauth2 + provider_id: discord + client_id: ${env:DISCORD_CLIENT_ID} + client_secret: ${env:DISCORD_CLIENT_SECRET} + + - id: default-dropbox + description: "The default Dropbox provider" + enabled: false + type: oauth2 + provider_id: dropbox + client_id: ${env:DROPBOX_CLIENT_ID} + client_secret: ${env:DROPBOX_CLIENT_SECRET} + + - id: default-github + description: "The default GitHub provider" + enabled: false + type: oauth2 + provider_id: github + client_id: ${env:GITHUB_CLIENT_ID} + client_secret: ${env:GITHUB_CLIENT_SECRET} + + - id: default-google + description: "The default Google provider" + enabled: false + type: oauth2 + provider_id: google + client_id: ${env:GOOGLE_CLIENT_ID} + client_secret: ${env:GOOGLE_CLIENT_SECRET} + + - id: default-linkedin + description: "The default LinkedIn provider" + enabled: false + type: oauth2 + provider_id: linkedin + client_id: ${env:LINKEDIN_CLIENT_ID} + client_secret: ${env:LINKEDIN_CLIENT_SECRET} + + - id: default-microsoft + description: "The default Microsoft provider" + enabled: false + type: oauth2 + provider_id: microsoft + client_id: ${env:MICROSOFT_CLIENT_ID} + client_secret: ${env:MICROSOFT_CLIENT_SECRET} + + - id: default-slack + description: "The default Slack provider" + enabled: false + type: oauth2 + provider_id: slack + client_id: ${env:SLACK_CLIENT_ID} + client_secret: ${env:SLACK_CLIENT_SECRET} + + - id: default-spotify + description: "The default Spotify provider" + enabled: false + type: oauth2 + provider_id: spotify + client_id: ${env:SPOTIFY_CLIENT_ID} + client_secret: ${env:SPOTIFY_CLIENT_SECRET} + + - id: default-x + description: "The default X provider" + enabled: false + type: oauth2 + provider_id: x + client_id: ${env:X_CLIENT_ID} + client_secret: ${env:X_CLIENT_SECRET} + + - id: default-zoom + description: "The default Zoom provider" + enabled: false + type: oauth2 + provider_id: zoom + client_id: ${env:ZOOM_CLIENT_ID} + client_secret: ${env:ZOOM_CLIENT_SECRET} diff --git a/docker/env.example b/docker/env.example new file mode 100644 index 00000000..4329cefc --- /dev/null +++ b/docker/env.example @@ -0,0 +1,48 @@ + +### LLM ### + +OPENAI_API_KEY= +ANTHROPIC_API_KEY= + +### Auth providers ### + +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= + +GOOGLE_CLIENT_ID="" +GOOGLE_CLIENT_SECRET= + +LINKEDIN_CLIENT_ID= +LINKEDIN_CLIENT_SECRET="" + +MICROSOFT_CLIENT_ID= +MICROSOFT_CLIENT_SECRET="" + +SLACK_CLIENT_ID="" +SLACK_CLIENT_SECRET= + +SPOTIFY_CLIENT_ID= +SPOTIFY_CLIENT_SECRET= + +X_CLIENT_ID= +X_CLIENT_SECRET= + +ZOOM_CLIENT_ID= +ZOOM_CLIENT_SECRET= + +### WORKER ### + +WORKER_URL="http://arcade-worker:8002" + +### Redis ### + +REDIS_HOST=arcade-redis +REDIS_PORT=6379 +REDIS_PASSWORD="" +RATE_LIMIT_REQS_PER_MIN=100 + +### ARCADE ### + +ARCADE_HOME=/app +ARCADE_WORKER_SECRET=dev +ROOT_KEY_1=supersecret diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 00000000..f6bb98ff --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,47 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /run/nginx.pid; + +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + client_max_body_size 5M; + client_body_buffer_size 5M; + + gzip on; + gzip_comp_level 2; + gzip_types text/plain text/css text/javascript application/javascript application/xml image/jpeg image/gif image/png; + gzip_vary on; + + keepalive_timeout 300; + + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name arcade-engine:9099; + + root /app; + client_max_body_size 10m; + + location / { + proxy_pass http://arcade-engine:9099; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + } +} diff --git a/docker/start.sh b/docker/start.sh index b0d870c0..3dffb1ab 100644 --- a/docker/start.sh +++ b/docker/start.sh @@ -1,10 +1,4 @@ #!/bin/bash -echo "Installing toolkits..." -for toolkit in $(echo $TOOLKITS | tr "," " "); do - echo "Installing $toolkit..." - pip install $toolkit -done - echo "Starting arcade..." arcade workerup --host $HOST --port $PORT $([ "$OTEL_ENABLE" = "true" ] && echo "--otel-enable") diff --git a/docker/toolkits.txt b/docker/toolkits.txt new file mode 100644 index 00000000..4389f94f --- /dev/null +++ b/docker/toolkits.txt @@ -0,0 +1,11 @@ +arcade-code-sandbox +arcade-github +arcade-google +arcade-linkedin +arcade-math +arcade-search +arcade-slack +arcade-spotify +arcade-web +arcade-x +arcade-zoom diff --git a/examples/serving-tools/docker/Dockerfile b/examples/serving-tools/docker/Dockerfile new file mode 100644 index 00000000..3267cd9d --- /dev/null +++ b/examples/serving-tools/docker/Dockerfile @@ -0,0 +1,10 @@ +ARG VERSION=latest + +# Base worker image +FROM ghcr.io/arcadeai/worker-base:${VERSION} + +# Copy requirements and constraints +COPY toolkits.txt ./ + +# Install toolkits from file +RUN pip install -r toolkits.txt diff --git a/examples/serving-tools/docker/README.md b/examples/serving-tools/docker/README.md new file mode 100644 index 00000000..2ff483bb --- /dev/null +++ b/examples/serving-tools/docker/README.md @@ -0,0 +1,30 @@ +## Custom Worker Image + +This example shows how to build a custom worker image with toolkits. + +### Requirements + +- Docker + +### Build + +``` +docker build -t custom-worker:0.1.0 . +``` + +### Run + +``` +docker run -p 8002:8002 custom-worker:0.1.0 +``` + +### Change the Toolkits + +To change the toolkits, edit the `toolkits.txt` file. + +``` +arcade-google==0.1.0 +arcade-web==0.1.0 +arcade-zoom==0.1.2 +... +``` diff --git a/examples/serving-tools/docker/toolkits.txt b/examples/serving-tools/docker/toolkits.txt new file mode 100644 index 00000000..9bba922e --- /dev/null +++ b/examples/serving-tools/docker/toolkits.txt @@ -0,0 +1 @@ +arcade-google diff --git a/examples/serving-tools/modal/README.md b/examples/serving-tools/modal/README.md new file mode 100644 index 00000000..69907ff4 --- /dev/null +++ b/examples/serving-tools/modal/README.md @@ -0,0 +1,17 @@ +## Deploy a Custom Arcade Worker on Modal + +### Requirements + +- Python 3.10+ +- Modal CLI + +### Deploy + +```bash +cd examples/serving-tools +modal deploy run-arcade-worker.py +``` + +### Changing the Toolkits + +To change the toolkits, edit the `toolkits` list in the `run-arcade-worker.py` file. diff --git a/examples/serving-tools/modal/run-arcade-worker.py b/examples/serving-tools/modal/run-arcade-worker.py new file mode 100644 index 00000000..25e18a34 --- /dev/null +++ b/examples/serving-tools/modal/run-arcade-worker.py @@ -0,0 +1,39 @@ +import os + +from modal import App, Image, asgi_app + +# Define the FastAPI app +app = App("arcade-worker") + +toolkits = ["arcade-google", "arcade-slack"] + +image = ( + Image.debian_slim() + .pip_install("arcade-ai[fastapi]") + .pip_install(toolkits) + .pip_install("fastapi>=0.115.3") + .pip_install("uvicorn>=0.24.0") +) + + +@app.function(image=image) +@asgi_app() +def fastapi_app(): + from fastapi import FastAPI + + from arcade.sdk import Toolkit + from arcade.worker.fastapi.worker import FastAPIWorker + + web_app = FastAPI() + + # Initialize app and Arcade FastAPIWorker + worker_secret = os.environ.get("ARCADE_WORKER_SECRET", "dev") + worker = FastAPIWorker(web_app, secret=worker_secret) + + # Register toolkits we've installed + installed_toolkits = Toolkit.find_all_arcade_toolkits() + for toolkit in toolkits: + if toolkit in installed_toolkits: + worker.register_toolkit(toolkit) + + return web_app