feat: Add multi-stage docker build support for custom sandbox.Dockerfile (#746)

This commit is contained in:
Tolik Malibroda 2025-06-05 17:46:54 +02:00 committed by GitHub
parent a8ac9b1fac
commit 4d4cf0f2f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 85 additions and 27 deletions

View File

@ -77,6 +77,7 @@ When you create a `.gemini/settings.json` file for project-specific settings, or
- See the [Theming section in README.md](../../README.md#theming) for available theme names.
- **`sandbox`** (boolean or string):
- Controls whether and how to use sandboxing for tool execution.
- If a `.gemini/sandbox.Dockerfile` exists in your project, it will be used to build a custom sandbox image based on `gemini-cli-sandbox`.
- `true`: Enable default sandbox (see [README](../../README.md) for behavior).
- `false`: Disable sandboxing (WARNING: this is inherently unsafe).
- `"docker"` or `"podman"`: Explicitly choose container-based sandboxing command.
@ -276,6 +277,31 @@ This example demonstrates how you can provide general project context, specific
By understanding and utilizing these configuration layers and the hierarchical nature of context files, you can effectively manage the AI's memory and tailor the Gemini CLI's responses to your specific needs and projects.
## Sandboxing
The Gemini CLI can execute potentially unsafe operations (like shell commands and file modifications) within a sandboxed environment to protect your system.
Sandboxing is disabled by default, but you can enable it in a few ways:
- Using `--sandbox` or `-s` flag.
- Setting `GEMINI_SANDBOX` environment variable.
- Sandbox is enabled in `--yolo` mode by default.
By default, it uses a pre-built `gemini-cli-sandbox` Docker image.
For project-specific sandboxing needs, you can create a custom Dockerfile at `.gemini/sandbox.Dockerfile` in your project's root directory. This Dockerfile should be based on the base sandbox image:
```dockerfile
FROM gemini-cli-sandbox
# Add your custom dependencies or configurations here
# For example:
# RUN apt-get update && apt-get install -y some-package
# COPY ./my-config /app/my-config
```
When `.gemini/sandbox.Dockerfile` exists, the CLI will automatically build and use a custom image for your project.
## Theming
The Gemini CLI supports theming to customize its color scheme and appearance. Themes define colors for text, backgrounds, syntax highlighting, and other UI elements.

View File

@ -73,7 +73,9 @@ async function shouldUseCurrentUserInSandbox(): Promise<boolean> {
return false; // Default to false if no other condition is met
}
async function getSandboxImageName(): Promise<string> {
async function getSandboxImageName(
isCustomProjectSandbox: boolean,
): Promise<string> {
const packageJsonResult = await readPackageUp();
const packageJsonConfig = packageJsonResult?.packageJson.config as
| { sandboxImageUri?: string }
@ -81,7 +83,9 @@ async function getSandboxImageName(): Promise<string> {
return (
process.env.GEMINI_SANDBOX_IMAGE ??
packageJsonConfig?.sandboxImageUri ??
LOCAL_DEV_SANDBOX_IMAGE_NAME
(isCustomProjectSandbox
? LOCAL_DEV_SANDBOX_IMAGE_NAME + '-' + path.basename(path.resolve())
: LOCAL_DEV_SANDBOX_IMAGE_NAME)
);
}
@ -272,15 +276,23 @@ export async function start_sandbox(sandbox: string) {
// determine full path for gemini-cli to distinguish linked vs installed setting
const gcPath = execSync(`realpath $(which gemini)`).toString().trim();
const image = await getSandboxImageName();
const projectSandboxDockerfile = path.join(
SETTINGS_DIRECTORY_NAME,
'sandbox.Dockerfile',
);
const isCustomProjectSandbox = fs.existsSync(projectSandboxDockerfile);
const image = await getSandboxImageName(isCustomProjectSandbox);
const workdir = process.cwd();
// if BUILD_SANDBOX is set, then call scripts/build_sandbox.sh under gemini-cli repo
// if BUILD_SANDBOX is set or project-specific sandbox.Dockerfile provided,
// then call scripts/build_sandbox.sh under gemini-cli repo
//
// note this can only be done with binary linked from gemini-cli repo
if (process.env.BUILD_SANDBOX) {
if (process.env.BUILD_SANDBOX || isCustomProjectSandbox) {
if (!gcPath.includes('gemini-cli/packages/')) {
console.error(
'ERROR: cannot BUILD_SANDBOX using installed gemini binary; ' +
'ERROR: cannot build sandbox using installed gemini binary; ' +
'run `npm link ./packages/cli` under gemini-cli repo to switch to linked binary.',
);
process.exit(1);
@ -293,9 +305,9 @@ export async function start_sandbox(sandbox: string) {
SETTINGS_DIRECTORY_NAME,
'sandbox.Dockerfile',
);
if (fs.existsSync(projectSandboxDockerfile)) {
if (isCustomProjectSandbox) {
console.error(`using ${projectSandboxDockerfile} for sandbox`);
buildArgs += `-f ${path.resolve(projectSandboxDockerfile)}`;
buildArgs += `-s -f ${path.resolve(projectSandboxDockerfile)} -i ${image}`;
}
spawnSync(`cd ${gcRoot} && scripts/build_sandbox.sh ${buildArgs}`, {
stdio: 'inherit',

View File

@ -26,20 +26,26 @@ fi
CMD=$(scripts/sandbox_command.sh)
echo "using $CMD for sandboxing"
IMAGE=gemini-cli-sandbox
DOCKERFILE=Dockerfile
BASE_IMAGE=gemini-cli-sandbox
CUSTOM_IMAGE=''
BASE_DOCKERFILE=Dockerfile
CUSTOM_DOCKERFILE=''
SKIP_NPM_INSTALL_BUILD=false
while getopts "sf:" opt; do
while getopts "sf:i:" opt; do
case ${opt} in
s) SKIP_NPM_INSTALL_BUILD=true ;;
f)
DOCKERFILE=$OPTARG
CUSTOM_DOCKERFILE=$OPTARG
;;
i)
CUSTOM_IMAGE=$OPTARG
;;
\?)
echo "usage: $(basename "$0") [-s] [-f <dockerfile>]"
echo " -s: skip npm install + npm run build"
echo " -f <dockerfile>: use <dockerfile>"
echo " -f <dockerfile>: use <dockerfile> for custom image"
echo " -i <image>: use <image> name for custom image"
exit 1
;;
esac
@ -64,9 +70,6 @@ npm pack -w @gemini-code/core --pack-destination ./packages/core/dist &>/dev/nul
# give node user (used during installation, see Dockerfile) access to these files
chmod 755 packages/*/dist/gemini-code-*.tgz
# build container image & prune older unused images
echo "building $IMAGE ... (can be slow first time)"
# redirect build output to /dev/null unless VERBOSE is set
BUILD_STDOUT="/dev/null"
if [ -n "${VERBOSE:-}" ]; then
@ -76,8 +79,8 @@ fi
# initialize build arg array from BUILD_SANDBOX_FLAGS
read -r -a build_args <<<"${BUILD_SANDBOX_FLAGS:-}"
# append common build args
build_args+=(-f "$DOCKERFILE" -t "$IMAGE" .)
build_image() {
local -n build_args=$1
if [[ "$CMD" == "podman" ]]; then
# use empty --authfile to skip unnecessary auth refresh overhead
@ -88,5 +91,22 @@ elif [[ "$CMD" == "docker" ]]; then
else
$CMD build "${build_args[@]}" >$BUILD_STDOUT
fi
}
# build container images & prune older unused images
echo "building $BASE_IMAGE ... (can be slow first time)"
base_image_build_args=(${build_args[@]})
base_image_build_args+=(-f "$BASE_DOCKERFILE" -t "$BASE_IMAGE" .)
build_image base_image_build_args
echo "built $BASE_IMAGE"
if [[ -n "$CUSTOM_DOCKERFILE" && -n "$CUSTOM_IMAGE" ]]; then
echo "building $CUSTOM_IMAGE ... (can be slow first time)"
custom_image_build_args=(${build_args[@]})
custom_image_build_args+=(-f "$CUSTOM_DOCKERFILE" -t "$CUSTOM_IMAGE" .)
build_image custom_image_build_args
echo "built $CUSTOM_IMAGE"
fi
$CMD image prune -f >/dev/null
echo "built $IMAGE"