Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b228bc2
enhancement: generalising docker build and run for multicloud environ…
raflFaisal Oct 9, 2025
8d998eb
incorporating review comments by gemini
raflFaisal Oct 9, 2025
b1f0595
remove comma left as typo
raflFaisal Oct 9, 2025
bd7913a
fix pyink reported issues
raflFaisal Oct 9, 2025
06642ea
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Oct 11, 2025
003b10d
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Oct 13, 2025
3fc9044
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Oct 13, 2025
7cc3567
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Oct 15, 2025
ee2969c
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Oct 17, 2025
6077ff3
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Oct 25, 2025
32a1fb0
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Nov 3, 2025
3f63fa1
review comments: incorporating suggestions
faisalBooking Nov 4, 2025
481c197
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Nov 4, 2025
cfc29fc
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
hangfei Nov 7, 2025
72da1ba
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Nov 8, 2025
865735c
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Nov 8, 2025
4edc40a
feat: incorporating review comments, adding testcases for deployers
faisalBooking Nov 9, 2025
a96a1df
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
DeanChensj Nov 9, 2025
a1e6ff1
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Nov 9, 2025
1479a65
Add copyright and license information to base_deployer.py
raflFaisal Nov 9, 2025
08fdffd
Add copyright and license information
raflFaisal Nov 9, 2025
c047abf
Add copyright and license information
raflFaisal Nov 9, 2025
a930f2a
Add copyright and license to docker_deployer.py
raflFaisal Nov 9, 2025
94616b3
Add copyright and license information to dockerfile_template.py
raflFaisal Nov 9, 2025
42e2c43
Merge branch 'main' into raflFaisal/generaliseContainerEnvForAdkAgent
raflFaisal Nov 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,26 @@ coordinator = LlmAgent(
)
```

### 🚀 Deployment Options

Deploying the Agent Locally with Docker Container:

```bash
adk deploy docker --with_ui <agent-folder>
```

Deploying the Agent in Google Cloud (Cloud Run)

```bash
adk deploy cloud_run --with_ui <agent-folder>
```

You may set the following environment variables in adk command, or in a .env file instead.

```bash
adk deploy cloud_run --with_ui --env GOOGLE_GENAI_USE_VERTEXAI=1 <agent-folder>
```

### Development UI

A built-in development UI to help you test, evaluate, debug, and showcase your agent(s).
Expand Down
174 changes: 32 additions & 142 deletions src/google/adk/cli/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,13 @@
import subprocess
from typing import Final
from typing import Optional
from typing import Tuple

import click
from packaging.version import parse

_DOCKERFILE_TEMPLATE: Final[str] = """
FROM python:3.11-slim
WORKDIR /app

# Create a non-root user
RUN adduser --disabled-password --gecos "" myuser

# Switch to the non-root user
USER myuser

# Set up environment variables - Start
ENV PATH="/home/myuser/.local/bin:$PATH"

ENV GOOGLE_GENAI_USE_VERTEXAI=1
ENV GOOGLE_CLOUD_PROJECT={gcp_project_id}
ENV GOOGLE_CLOUD_LOCATION={gcp_region}

# Set up environment variables - End

# Install ADK - Start
RUN pip install google-adk=={adk_version}
# Install ADK - End

# Copy agent - Start

# Set permission
COPY --chown=myuser:myuser "agents/{app_name}/" "/app/agents/{app_name}/"

# Copy agent - End

# Install Agent Deps - Start
{install_agent_deps}
# Install Agent Deps - End

EXPOSE {port}

CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {allow_origins_option} {a2a_option} "/app/agents"
"""
from .config.dockerfile_template import _DOCKERFILE_TEMPLATE
from .deployers.deployer_factory import DeployerFactory

_AGENT_ENGINE_APP_TEMPLATE: Final[str] = """
from vertexai.preview.reasoning_engines import AdkApp
Expand Down Expand Up @@ -98,52 +63,6 @@ def _resolve_project(project_in_option: Optional[str]) -> str:
return project


def _validate_gcloud_extra_args(
extra_gcloud_args: Optional[tuple[str, ...]], adk_managed_args: set[str]
) -> None:
"""Validates that extra gcloud args don't conflict with ADK-managed args.

This function dynamically checks for conflicts based on the actual args
that ADK will set, rather than using a hardcoded list.

Args:
extra_gcloud_args: User-provided extra arguments for gcloud.
adk_managed_args: Set of argument names that ADK will set automatically.
Should include '--' prefix (e.g., '--project').

Raises:
click.ClickException: If any conflicts are found.
"""
if not extra_gcloud_args:
return

# Parse user arguments into a set of argument names for faster lookup
user_arg_names = set()
for arg in extra_gcloud_args:
if arg.startswith('--'):
# Handle both '--arg=value' and '--arg value' formats
arg_name = arg.split('=')[0]
user_arg_names.add(arg_name)

# Check for conflicts with ADK-managed args
conflicts = user_arg_names.intersection(adk_managed_args)

if conflicts:
conflict_list = ', '.join(f"'{arg}'" for arg in sorted(conflicts))
if len(conflicts) == 1:
raise click.ClickException(
f"The argument {conflict_list} conflicts with ADK's automatic"
' configuration. ADK will set this argument automatically, so please'
' remove it from your command.'
)
else:
raise click.ClickException(
f"The arguments {conflict_list} conflict with ADK's automatic"
' configuration. ADK will set these arguments automatically, so'
' please remove them from your command.'
)


def _get_service_option_by_adk_version(
adk_version: str,
session_uri: Optional[str],
Expand Down Expand Up @@ -171,9 +90,10 @@ def _get_service_option_by_adk_version(
return f'--session_db_url={session_uri}' if session_uri else ''


def to_cloud_run(
def run(
*,
agent_folder: str,
provider: str,
project: Optional[str],
region: Optional[str],
service_name: str,
Expand All @@ -190,6 +110,8 @@ def to_cloud_run(
artifact_service_uri: Optional[str] = None,
memory_service_uri: Optional[str] = None,
a2a: bool = False,
provider_args: Tuple[str],
env: Tuple[str],
extra_gcloud_args: Optional[tuple[str, ...]] = None,
):
"""Deploys an agent to Google Cloud Run.
Expand All @@ -209,6 +131,7 @@ def to_cloud_run(

Args:
agent_folder: The folder (absolute path) containing the agent source code.
provider: Target deployment platform (cloud_run, docker, etc).
project: Google Cloud project id.
region: Google Cloud region.
service_name: The service name in Cloud Run.
Expand All @@ -223,10 +146,14 @@ def to_cloud_run(
session_service_uri: The URI of the session service.
artifact_service_uri: The URI of the artifact service.
memory_service_uri: The URI of the memory service.
provider_args: The arguments specific to cloud provider
env: The environment valriables provided
"""
app_name = app_name or os.path.basename(agent_folder)
mode = 'web' if with_ui else 'api_server'
trace_to_cloud_option = '--trace_to_cloud' if trace_to_cloud else ''

click.echo(f'Start generating Cloud Run source files in {temp_folder}')
click.echo(f'Start generating deployment files in {temp_folder}')

# remove temp_folder if exists
if os.path.exists(temp_folder):
Expand Down Expand Up @@ -258,15 +185,15 @@ def to_cloud_run(
gcp_region=region,
app_name=app_name,
port=port,
command='web' if with_ui else 'api_server',
command=mode,
install_agent_deps=install_agent_deps,
service_option=_get_service_option_by_adk_version(
adk_version,
session_service_uri,
artifact_service_uri,
memory_service_uri,
),
trace_to_cloud_option='--trace_to_cloud' if trace_to_cloud else '',
trace_to_cloud_option=trace_to_cloud_option,
allow_origins_option=allow_origins_option,
adk_version=adk_version,
host_option=host_option,
Expand All @@ -279,61 +206,24 @@ def to_cloud_run(
dockerfile_content,
)
click.echo(f'Creating Dockerfile complete: {dockerfile_path}')
click.echo(f'Deploying to {provider}...')

deployer = DeployerFactory.get_deployer(provider)
deployer.deploy(
agent_folder=agent_folder,
temp_folder=temp_folder,
service_name=service_name,
provider_args=provider_args,
env_vars=env,
project=project,
region=region,
port=port,
verbosity=verbosity,
extra_gcloud_args=extra_gcloud_args,
log_level=log_level,
)

# Deploy to Cloud Run
click.echo('Deploying to Cloud Run...')
region_options = ['--region', region] if region else []
project = _resolve_project(project)

# Build the set of args that ADK will manage
adk_managed_args = {'--source', '--project', '--port', '--verbosity'}
if region:
adk_managed_args.add('--region')

# Validate that extra gcloud args don't conflict with ADK-managed args
_validate_gcloud_extra_args(extra_gcloud_args, adk_managed_args)

# Build the command with extra gcloud args
gcloud_cmd = [
'gcloud',
'run',
'deploy',
service_name,
'--source',
temp_folder,
'--project',
project,
*region_options,
'--port',
str(port),
'--verbosity',
log_level.lower() if log_level else verbosity,
]

# Handle labels specially - merge user labels with ADK label
user_labels = []
extra_args_without_labels = []

if extra_gcloud_args:
for arg in extra_gcloud_args:
if arg.startswith('--labels='):
# Extract user-provided labels
user_labels_value = arg[9:] # Remove '--labels=' prefix
user_labels.append(user_labels_value)
else:
extra_args_without_labels.append(arg)

# Combine ADK label with user labels
all_labels = ['created-by=adk']
all_labels.extend(user_labels)
labels_arg = ','.join(all_labels)

gcloud_cmd.extend(['--labels', labels_arg])

# Add any remaining extra passthrough args
gcloud_cmd.extend(extra_args_without_labels)

subprocess.run(gcloud_cmd, check=True)
click.echo(f'Deployment to {provider} complete.')
finally:
click.echo(f'Cleaning up the temp folder: {temp_folder}')
shutil.rmtree(temp_folder)
Expand Down
Loading
Loading