Skip to content
13 changes: 7 additions & 6 deletions .github/workflows/ci_job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
dev-tests:
name: Full test suite on dev stack (Python 3.13)
runs-on: ubuntu-latest
env:
COMPOSE_FILE: docker/docker-compose.dev.yml
COMPOSE_ENV_FILES: .env

steps:
- name: Checkout
Expand Down Expand Up @@ -85,6 +88,9 @@ jobs:
prod-smoke:
name: Production smoke test
runs-on: ubuntu-latest
env:
COMPOSE_FILE: docker/docker-compose.yml:docker/docker-compose.ci.yml
COMPOSE_ENV_FILES: .env

steps:
- name: Checkout
Expand All @@ -109,16 +115,11 @@ jobs:

- name: Start production stack
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.ci.yml \
up climateclaw code-server web-search-server \
docker compose up climateclaw code-server web-search-server \
-d --wait --build --remove-orphans || {
docker ps -a

docker compose \
-f docker-compose.yml \
-f docker-compose.ci.yml \
logs climateclaw mongodb code-server web-search-server litellm

exit 1
Expand Down
28 changes: 12 additions & 16 deletions dev.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "${ROOT_DIR}"
COMPOSE_PROJECT_NAME="${ROOT_DIR##*/}"

# ------------------------------------------------------------------
# Simple dev launcher for ClimateClaw
#
Expand All @@ -23,7 +27,7 @@ Custom options:
--debug, --DEBUG Enable debug mode (CLIMATECLAW_DEBUG=1)
--debug=VALUE Set debug explicitly, e.g. --debug=0 or --debug=1
--no-debug Disable debug mode
--scale Generate and use docker-compose.dev.scaled.yml
--scale Generate and use docker/docker-compose.dev.scaled.yml
--build Build images before starting

Examples:
Expand All @@ -43,9 +47,10 @@ EOF
export CLIMATECLAW_DEV=1

CLIMATECLAW_DEBUG="${CLIMATECLAW_DEBUG:-0}"
COMPOSE_FILE="docker-compose.dev.yml"
ENV_FILE=".env"
COMPOSE_FILE="docker/docker-compose.dev.yml"
BUILD_COMPOSE_FILE="${COMPOSE_FILE}"
SCALED_COMPOSE_FILE="docker-compose.dev.scaled.yml"
SCALED_COMPOSE_FILE="docker/docker-compose.dev.scaled.yml"
DO_BUILD=0
COMPOSE_ARGS=()

Expand All @@ -70,7 +75,7 @@ for arg in "$@"; do
;;
# Launch with scaling and proxy
--scale)
./gen_compose.py ${COMPOSE_FILE}
./docker/gen_compose.py "${COMPOSE_FILE}"
COMPOSE_FILE="${SCALED_COMPOSE_FILE}"
;;
# Build images once from the unscaled compose file.
Expand All @@ -90,18 +95,9 @@ export CLIMATECLAW_DEBUG
echo "[dev.sh] Using ${COMPOSE_FILE} with DEBUG=${CLIMATECLAW_DEBUG}"
echo "[dev.sh] docker compose -f ${COMPOSE_FILE} ${COMPOSE_ARGS[*]}"

if [[ " ${COMPOSE_ARGS[*]} " == *" up "* ]]; then
if [ -f "${SCALED_COMPOSE_FILE}" ]; then
echo "[dev.sh] Tearing down scaled dev stack"
docker compose -f "${SCALED_COMPOSE_FILE}" down
fi
echo "[dev.sh] Tearing down dev stack"
docker compose -f "${BUILD_COMPOSE_FILE}" down
fi

docker compose -f "${BUILD_COMPOSE_FILE}" --profile build-only build climateclaw-base
docker compose --project-name "${COMPOSE_PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${BUILD_COMPOSE_FILE}" --profile build-only build climateclaw-base
if [ "${DO_BUILD}" = "1" ]; then
echo "[dev.sh] Building images from ${BUILD_COMPOSE_FILE}"
docker compose -f "${BUILD_COMPOSE_FILE}" build
docker compose --project-name "${COMPOSE_PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${BUILD_COMPOSE_FILE}" build
fi
docker compose -f "${COMPOSE_FILE}" "${COMPOSE_ARGS[@]}"
docker compose --project-name "${COMPOSE_PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" "${COMPOSE_ARGS[@]}"
File renamed without changes.
45 changes: 22 additions & 23 deletions docker-compose.dev.yml → docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
services:
climateclaw-base:
build:
context: .
context: ..
dockerfile: docker/Dockerfile.base
image: ghcr.io/freva-org/climateclaw-base:latest
profiles:
Expand All @@ -10,9 +10,9 @@ services:
climateclaw:
image: climateclaw-${CLIMATECLAW_INSTANCE_NAME:?Instance name must be set in the .env file to differentiate between instances}
build:
context: .
context: ..
dockerfile: docker/climateclaw/Dockerfile
env_file: .env
env_file: ../.env
environment:
CLIMATECLAW_DEV: ${CLIMATECLAW_DEV}
CLIMATECLAW_DEBUG: ${CLIMATECLAW_DEBUG:-0}
Expand All @@ -22,8 +22,8 @@ services:
- "8600:${CLIMATECLAW_BACKEND_PORT:-8502}" # localhost:8600/docs give FastAPI docs
- "${CLIMATECLAW_DEBUG_PORT:-5678}:${CLIMATECLAW_DEBUG_PORT:-5678}" # Port for debugpy
volumes:
- ./cache/:/app/cache
- ./logs/:/app/logs
- ../cache/:/app/cache
- ../logs/:/app/logs
networks:
- climateclaw
healthcheck:
Expand All @@ -41,27 +41,27 @@ services:
rag-server:
image: rag-server-${CLIMATECLAW_INSTANCE_NAME}
build:
context: .
context: ..
dockerfile: docker/rag-server/Dockerfile
env_file: .env
env_file: ../.env
hostname: rag-server-${CLIMATECLAW_INSTANCE_NAME}
environment:
CLIMATECLAW_DEV: ${CLIMATECLAW_DEV}
CLIMATECLAW_DEBUG: ${CLIMATECLAW_DEBUG:-0}
CLIMATECLAW_MCP_HOST: 0.0.0.0
CLIMATECLAW_MCP_PORT: ${CLIMATECLAW_RAG_PORT:-8050}
volumes:
- ./resources:/app/resources:ro
- ./logs/:/app/logs
- ../resources:/app/resources:ro
- ../logs/:/app/logs
networks:
- climateclaw

code-server:
image: code-server-${CLIMATECLAW_INSTANCE_NAME}
build:
context: .
context: ..
dockerfile: docker/code-server/Dockerfile
env_file: .env
env_file: ../.env
hostname: code-server-${CLIMATECLAW_INSTANCE_NAME}
environment:
CLIMATECLAW_DEV: ${CLIMATECLAW_DEV}
Expand All @@ -72,8 +72,8 @@ services:
- 8051:8051
cpus: 4
volumes:
- ./cache/:/app/cache
- ./logs/:/app/logs
- ../cache/:/app/cache
- ../logs/:/app/logs
networks:
- climateclaw
healthcheck:
Expand All @@ -86,9 +86,9 @@ services:
web-search-server:
image: web-search-server-${CLIMATECLAW_INSTANCE_NAME}
build:
context: .
context: ..
dockerfile: docker/web-search-server/Dockerfile
env_file: .env
env_file: ../.env
hostname: web-search-server-${CLIMATECLAW_INSTANCE_NAME}
environment:
CLIMATECLAW_DEV: ${CLIMATECLAW_DEV}
Expand All @@ -100,7 +100,7 @@ services:
networks:
- climateclaw
volumes:
- ./logs/:/app/logs
- ../logs/:/app/logs
healthcheck:
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8052/healthz')\""]
interval: 30s
Expand All @@ -112,8 +112,8 @@ services:
image: ghcr.io/berriai/litellm:main-latest
hostname: litellm-${CLIMATECLAW_INSTANCE_NAME}
volumes:
- ./litellm_config.dev.yaml:/app/config.yaml # Development config excludes the deployment-only vLLM model
- ./logs/:/app/logs
- ../litellm_config.dev.yaml:/app/config.yaml # Development config excludes the deployment-only vLLM model
- ../logs/:/app/logs
entrypoint: ["/bin/sh", "-c"]
command:
- >
Expand All @@ -122,9 +122,8 @@ services:
--port=4000
--num_workers=8
--run_gunicorn
--detailed_debug
2>&1 | tee -a /app/logs/litellm.log
env_file: .env
env_file: ../.env
ports:
- "4000:4000" # Expose LiteLLM port to the host machine for direct access if needed, e.g. local dev
networks:
Expand All @@ -148,7 +147,7 @@ services:
ports:
- "27017:27017"
volumes:
- ./mongo-data:/data/db
- ../mongo-data:/data/db
healthcheck:
test: ["CMD", "healthchecks", "-s", "mongo"]
interval: 35s
Expand Down Expand Up @@ -195,8 +194,8 @@ services:
- DEV_MODE=1
- CHAT_BOT=1
volumes:
- ./docker/freva.conf:/data/evaluation_system.conf
- ./cache/:/opt/freva_web/static/preview/climateclaw
- ./freva.conf:/data/evaluation_system.conf
- ../cache/:/opt/freva_web/static/preview/climateclaw

freva-cache:
networks:
Expand Down
20 changes: 10 additions & 10 deletions docker-compose.yml → docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ services:

climateclaw-base:
# Build manually with:
# docker compose -f docker-compose.dev.yml --profile build-only build climateclaw-base
# docker compose -f docker/docker-compose.dev.yml --profile build-only build climateclaw-base
build:
context: .
context: ..
dockerfile: docker/Dockerfile.base
image: ghcr.io/freva-org/climateclaw-base:latest
profiles:
Expand All @@ -13,9 +13,9 @@ services:
climateclaw:
image: climateclaw-${CLIMATECLAW_INSTANCE_NAME:?Instance name must be set in the .env file to differentiate between instances}
build:
context: .
context: ..
dockerfile: docker/climateclaw/Dockerfile
env_file: .env
env_file: ../.env
hostname: climateclaw-${CLIMATECLAW_INSTANCE_NAME}
ports:
- "${CLIMATECLAW_TARGET_PORT:-8502}:${CLIMATECLAW_BACKEND_PORT:-8502}" # If either is unset, the default value will be used.
Expand All @@ -38,9 +38,9 @@ services:
code-server:
image: code-server-${CLIMATECLAW_INSTANCE_NAME}
build:
context: .
context: ..
dockerfile: docker/code-server/Dockerfile
env_file: .env
env_file: ../.env
hostname: code-server-${CLIMATECLAW_INSTANCE_NAME}
environment:
CLIMATECLAW_MCP_HOST: 0.0.0.0
Expand All @@ -62,9 +62,9 @@ services:
web-search-server:
image: web-search-server-${CLIMATECLAW_INSTANCE_NAME}
build:
context: .
context: ..
dockerfile: docker/web-search-server/Dockerfile
env_file: .env
env_file: ../.env
hostname: web-search-server-${CLIMATECLAW_INSTANCE_NAME}
environment:
CLIMATECLAW_MCP_HOST: 0.0.0.0
Expand All @@ -84,7 +84,7 @@ services:
image: ghcr.io/berriai/litellm:v1.102.0
hostname: litellm-${CLIMATECLAW_INSTANCE_NAME}
volumes:
- ./litellm_config.yaml:/app/config.yaml # Mount the local litellm_config.yaml file to the container
- ../litellm_config.yaml:/app/config.yaml # Mount the local litellm_config.yaml file to the container
- /container/da/climateclaw-links/${CLIMATECLAW_INSTANCE_NAME}/logs:/app/logs
entrypoint: ["/bin/sh", "-c"]
command:
Expand All @@ -96,7 +96,7 @@ services:
--run_gunicorn
--detailed_debug
2>&1 | tee -a /app/logs/litellm.log
env_file: .env
env_file: ../.env
environment:
CLIMATECLAW_VLLM_LITELLM_MODEL: "hosted_vllm/${CLIMATECLAW_VLLM_MODEL_ID:-nvidia/Gemma-4-31B-IT-NVFP4}"
networks:
Expand Down
9 changes: 5 additions & 4 deletions gen_compose.py → docker/gen_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def generate_haproxy(
def main():

if len(sys.argv) < 2:
print("Usage: gen_compose.py docker-compose.dev.yml [project]")
print("Usage: docker/gen_compose.py docker/docker-compose.dev.yml [project]")
sys.exit(1)

compose_path = sys.argv[1]
Expand Down Expand Up @@ -390,7 +390,7 @@ def main():
}

log_dir = (
"./logs/"
"../logs/"
if DEV_MODE
else "/container/da/climateclaw-links/${CLIMATECLAW_INSTANCE_NAME}/logs"
)
Expand Down Expand Up @@ -439,9 +439,10 @@ def main():
timeout=mcp_request_timeout,
)

Path("haproxy.cfg").write_text(haproxy_cfg)
haproxy_path = input_path.with_name("haproxy.cfg")
haproxy_path.write_text(haproxy_cfg)

print(f"Generated {output_path.name} and haproxy.cfg")
print(f"Generated {output_path} and {haproxy_path}")


if __name__ == "__main__":
Expand Down
23 changes: 14 additions & 9 deletions prod.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "${ROOT_DIR}"
COMPOSE_PROJECT_NAME="${ROOT_DIR##*/}"

usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS] [COMPOSE_ARGS...]

Deploy the climateclaw stack via podman-compose.

Generates a scaled compose file from docker-compose.yml, tears down any
Generates a scaled compose file from docker/docker-compose.yml, tears down any
previous deployment, optionally rebuilds images, and starts the services.

Options:
Expand All @@ -32,8 +36,8 @@ EOF
exit 0
}

COMPOSE_FILE="docker-compose.yml"
SCALED_FILE="docker-compose.scaled.yml"
COMPOSE_FILE="docker/docker-compose.yml"
SCALED_FILE="docker/docker-compose.scaled.yml"
ENV_FILE=".env"
PROJECT="${CLIMATECLAW_PROJECT_NAME:-}"
do_build=false
Expand Down Expand Up @@ -122,22 +126,23 @@ fi
# --- Generate scaled compose file ---
echo "[prod.sh] Generating scaled compose file from ${COMPOSE_FILE}"
echo "[prod.sh] Using project: ${CLIMATECLAW_PROJECT_NAME}"
./gen_compose.py "${COMPOSE_FILE}" "${PROJECT}"
echo "[prod.sh] Using Compose project: ${COMPOSE_PROJECT_NAME}"
./docker/gen_compose.py "${COMPOSE_FILE}" "${PROJECT}"

# --- Tear down previous deployment ---
${COMPOSE} -f "${SCALED_FILE}" down
${COMPOSE} --project-name "${COMPOSE_PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${SCALED_FILE}" down

echo "[prod.sh] Building climateclaw-base from ${COMPOSE_FILE}"
${COMPOSE} -f "${COMPOSE_FILE}" --profile build-only build climateclaw-base
${COMPOSE} --project-name "${COMPOSE_PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" --profile build-only build climateclaw-base

if [ "$do_build" = true ]; then
echo "[prod.sh] Building images ..."
${COMPOSE} -f "${COMPOSE_FILE}" build
${COMPOSE} --project-name "${COMPOSE_PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" build
fi

# --- Start ---
if [ ${#args[@]} -eq 0 ]; then
args=(up -d)
fi
echo "[prod.sh] Starting: ${COMPOSE} -f ${SCALED_FILE} ${args[*]}"
${COMPOSE} -f "${SCALED_FILE}" "${args[@]}"
echo "[prod.sh] Starting: ${COMPOSE} --project-name ${COMPOSE_PROJECT_NAME} --env-file ${ENV_FILE} -f ${SCALED_FILE} ${args[*]}"
${COMPOSE} --project-name "${COMPOSE_PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${SCALED_FILE}" "${args[@]}"
Loading
Loading