Full Flow Example
This walkthrough demonstrates the complete IdeGYM workflow end-to-end:
- Define a custom environment image using the Python API
- Build and push it via Kaniko (in-cluster)
- Start an IdeGYM server from the built image
- Execute commands inside the running environment
- Tear down the server
Two scenarios are covered:
- Scenario A — build on top of a pre-built IdeGYM base image (fast, recommended for most cases)
- Scenario B — start from plain Debian and install IdeGYM from source using
IdeGYMServer.from_git(useful when you want to pin to a specific IdeGYM commit or don't have access to GHCR)
For local Docker builds and loading images into Minikube without a registry, see Local Deployment.
Prerequisites
- A running cluster with IdeGYM deployed (see Local Deployment or Remote Deployment)
- Project dependencies installed:
uv sync --all-packages --all-extras --all-groups - The orchestrator reachable at the configured URL
Scenario A: Pre-built IdeGYM base image
Start from an image that already has the IdeGYM server installed, and layer your user, project, and any extra commands on top. This is the fastest path since the server installation is already baked into the base.
1. Define the image
from idegym.image.builder import Image
from idegym.plugins.defaults.image import Permissions, Project, User
BASE_IMAGE = "ghcr.io/jetbrains-research/idegym/server-debian-bookworm-20250520-slim:latest"
# When deploying to Minikube via e2e tests, use the cluster-local registry:
# BASE_IMAGE = "registry.kube-system.svc.cluster.local/server-debian-bookworm-20250520-slim:latest"
image = (
Image.from_base(BASE_IMAGE)
.with_plugin(User(username="devuser", uid=2000, gid=2000, sudo=True))
.with_plugin(Permissions(paths={"/home/devuser": {"owner": "devuser", "mode": "755"}}))
.with_plugin(
Project.from_git(
url="https://github.com/owner/my-repo.git",
ref="abc123def456",
owner="devuser",
target="/home/devuser/project",
)
)
.run_commands("echo 'environment ready' > /home/devuser/ready.txt")
.with_runtime(
runtime_class_name="gvisor",
resources={
"requests": {"cpu": "500m", "memory": "512Mi", "ephemeral-storage": "1Gi"},
"limits": {"cpu": "500m", "memory": "512Mi", "ephemeral-storage": "1Gi"},
},
)
)
2. Build, start, and run
import asyncio
from pathlib import Path
import tempfile
from idegym.api.auth import BasicAuth
from idegym.api.cpu import CpuQuantity
from idegym.api.memory import MemoryQuantity
from idegym.api.resources import KubernetesResources, ResourceQuantities
from idegym.client.client import IdeGYMClient
async def main():
async with IdeGYMClient(
orchestrator_url="http://idegym-local.test",
name="my-client",
namespace="idegym-local",
auth=BasicAuth(username="test", password="test"),
) as client:
# Submit a Kaniko build job and wait for it to finish.
with tempfile.TemporaryDirectory() as tmp:
yaml_path = image.write_yaml(Path(tmp) / "image.yaml")
summary = await client.jobs.build_and_push_images(
path=yaml_path,
namespace="idegym-local",
timeout=600,
poll_interval=10,
)
if summary.failed_jobs > 0:
raise RuntimeError(f"Build failed: {summary.jobs_results[0].details}")
image_tag = summary.jobs_results[0].tag
print(f"Built image: {image_tag}")
# Start the server and run commands inside it.
async with client.with_server(
image_tag=image_tag,
server_name="my-dev-server",
runtime_class_name="gvisor",
run_as_root=True,
resources=KubernetesResources(
requests=ResourceQuantities(
cpu=CpuQuantity(millicores=500),
memory=MemoryQuantity(gi=1),
),
limits=ResourceQuantities(
cpu=CpuQuantity(cores=1),
memory=MemoryQuantity(gi=2),
),
),
server_start_wait_timeout_in_seconds=600,
) as server:
result = await server.execute_bash(script="id devuser", command_timeout=30.0)
print(f"User: {result.stdout.strip()}")
result = await server.execute_bash(script="ls /home/devuser/project/", command_timeout=30.0)
print(f"Project files: {result.stdout.strip()}")
# Server is stopped automatically when the `with_server` block exits.
print("Done.")
asyncio.run(main())
Scenario B: Build IdeGYM from source
Use this when you need to pin to a specific IdeGYM commit, or when the pre-built GHCR image
is not available. IdeGYMServer.from_git clones the IdeGYM repository inside the container
at build time and installs the server from source — no pre-built base image required.
Requires git in the base image. BaseSystem() (the default package set) already includes it.
1. Define the image
from idegym.image.builder import Image
from idegym.plugins.defaults.image import BaseSystem, IdeGYMServer, Project, User
IDEGYM_REPO = "https://github.com/jetbrains-research/idegym-oss.git"
IDEGYM_REF = "main" # pin to a tag or commit SHA for reproducibility
image = (
Image.from_base("debian:bookworm-slim")
.with_plugin(BaseSystem()) # installs git, curl, dumb-init, etc.
.with_plugin(User(username="devuser", uid=2000, gid=2000, sudo=True))
.with_plugin(IdeGYMServer.from_git(url=IDEGYM_REPO, ref=IDEGYM_REF))
.with_plugin(
Project.from_git(
url="https://github.com/owner/my-repo.git",
ref="abc123def456",
owner="devuser",
target="/home/devuser/project",
)
)
.run_commands("echo 'environment ready' > /home/devuser/ready.txt")
.with_runtime(
runtime_class_name="gvisor",
resources={
"requests": {"cpu": "500m", "memory": "1Gi", "ephemeral-storage": "2Gi"},
"limits": {"cpu": "500m", "memory": "1Gi", "ephemeral-storage": "2Gi"},
},
)
)
Note: Building from source takes longer than using a pre-built base image because
uv python installanduv syncrun inside the Kaniko job. Allocate extratimeoutin the build call if needed.
2. Build, start, and run
The build and server usage is identical to Scenario A — just substitute this image definition:
import asyncio
from pathlib import Path
import tempfile
from idegym.api.auth import BasicAuth
from idegym.api.cpu import CpuQuantity
from idegym.api.memory import MemoryQuantity
from idegym.api.resources import KubernetesResources, ResourceQuantities
from idegym.client.client import IdeGYMClient
async def main():
async with IdeGYMClient(
orchestrator_url="http://idegym-local.test",
name="my-client",
namespace="idegym-local",
auth=BasicAuth(username="test", password="test"),
) as client:
with tempfile.TemporaryDirectory() as tmp:
yaml_path = image.write_yaml(Path(tmp) / "image.yaml")
summary = await client.jobs.build_and_push_images(
path=yaml_path,
namespace="idegym-local",
timeout=1200, # allow extra time for source build
poll_interval=15,
)
if summary.failed_jobs > 0:
raise RuntimeError(f"Build failed: {summary.jobs_results[0].details}")
image_tag = summary.jobs_results[0].tag
print(f"Built image: {image_tag}")
async with client.with_server(
image_tag=image_tag,
server_name="my-from-source-server",
runtime_class_name="gvisor",
run_as_root=True,
resources=KubernetesResources(
requests=ResourceQuantities(
cpu=CpuQuantity(millicores=500),
memory=MemoryQuantity(gi=1),
),
limits=ResourceQuantities(
cpu=CpuQuantity(cores=1),
memory=MemoryQuantity(gi=2),
),
),
server_start_wait_timeout_in_seconds=600,
) as server:
result = await server.execute_bash(script="cat /home/devuser/ready.txt", command_timeout=30.0)
print(f"Ready: {result.stdout.strip()}")
asyncio.run(main())
Inspecting the generated Dockerfile
Before submitting a build, you can inspect the Dockerfile that the image definition will produce:
spec = image.to_spec()
print(spec.dockerfile_content)
For Scenario A this produces something like:
FROM ghcr.io/jetbrains-research/idegym/server-debian-bookworm-20250520-slim:latest
SHELL ["/bin/bash", "-c"]
USER root
ARG IDEGYM_PROJECT_ARCHIVE_URL
ARG IDEGYM_PROJECT_ARCHIVE_PATH
ARG IDEGYM_AUTH_TOKEN
ARG IDEGYM_AUTH_TYPE
ENV IDEGYM_PROJECT_ARCHIVE_URL="$IDEGYM_PROJECT_ARCHIVE_URL"
ENV IDEGYM_PROJECT_ARCHIVE_PATH="$IDEGYM_PROJECT_ARCHIVE_PATH"
ENV IDEGYM_PROJECT_ROOT="/home/devuser/project"
# Create user devuser (uid=2000)
RUN set -eux; \
groupadd --gid 2000 devuser; \
useradd --uid 2000 --gid 2000 --shell /bin/bash --create-home devuser; \
echo 'devuser ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
# Set permissions
RUN set -eux; \
chown devuser:devuser /home/devuser && chmod 755 /home/devuser
# Download project
RUN set -eux; \
...
USER devuser
RUN set -eux; \
echo 'environment ready' > /home/devuser/ready.txt
For Scenario B (IdeGYMServer.from_git) the generated Dockerfile is much longer — it
includes the git clone, file-copy setup, uv python install, uv sync, and the
ENTRYPOINT/HEALTHCHECK directives that the base image already provides in Scenario A.
What the orchestrator does internally
When client.jobs.build_and_push_images() is called:
- The client uploads the YAML to
POST /api/jobs/build-images - The orchestrator parses the YAML into
Imageobjects and callsimage.to_spec()on each - For each image the orchestrator creates a Kubernetes Job running a Kaniko container:
dockerfile_contentis passed as the Dockerfile- Download ARGs (project URL, auth token) are passed as
--build-argvalues --destinationpoints to the cluster-internal registry
- The orchestrator polls the jobs and returns a summary with the pushed image tags
- The client returns the summary to the caller
Next Steps
- Image Builder — full plugin reference and YAML format
- Client Library — complete API for starting servers and running tasks
- Local Deployment — set up Minikube, including local Docker builds
- Remote Deployment — deploy to a production cluster
- E2E Tests — runnable end-to-end examples