Layer project Dockerfile on top of base image
Always builds ramekin-agent first, so .ramekin/Dockerfile can
FROM ramekin-agent and add only project-specific dependencies.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/README.md b/README.md
index 94c9b8c..ac0416f 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,12 @@ A built-in pi extension (`ramekin.ts`) is mounted into the agent container. It a
### Custom Dockerfile
-Place a `Dockerfile` at `.ramekin/Dockerfile` in your workspace to override the default agent image. The workspace is used as the build context, so `COPY` instructions work relative to the project root.
+Place a `Dockerfile` at `.ramekin/Dockerfile` in your workspace to extend the base agent image. Use `FROM ramekin-agent` to layer on top — the base image includes Node.js, pi, git, ripgrep, and fd. The workspace is used as the build context, so `COPY` instructions work relative to the project root.
+
+```dockerfile
+FROM ramekin-agent
+RUN apt-get update && apt-get install -y ruby && rm -rf /var/lib/apt/lists/*
+```
## Development
diff --git a/src/main.rs b/src/main.rs
index 3fba218..0f48e14 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -75,14 +75,27 @@ fn run() -> Result<()> {
let compose_file = cache_dir.join("compose.yml");
- // Use .ramekin/Dockerfile from workspace if it exists, otherwise use the embedded one
+ // Always build the base image first
+ info!("building base image");
+ let base_dockerfile = cache_dir.join("Dockerfile");
+ let status = Command::new("docker")
+ .args(["build", "-t", "ramekin-agent", "-f"])
+ .arg(&base_dockerfile)
+ .arg(&cache_dir)
+ .status()
+ .wrap_err("failed to build base image")?;
+
+ if !status.success() {
+ bail!("base image build failed ({})", status);
+ }
+
+ // If a project Dockerfile exists, build it on top of the base image
let custom_dockerfile = workspace.join(".ramekin/Dockerfile");
let (dockerfile, build_context) = if custom_dockerfile.exists() {
- info!("using custom Dockerfile from .ramekin/Dockerfile");
+ info!("building project image from .ramekin/Dockerfile");
(custom_dockerfile, workspace.clone())
} else {
- info!("using built-in Dockerfile");
- (cache_dir.join("Dockerfile"), cache_dir.clone())
+ (base_dockerfile, cache_dir.clone())
};
let docker_compose = |args: &[&str]| -> Result<Command> {