Switch from docker run to docker compose
Static compose.yml replaces runtime docker build + docker run.
Easier to inspect and sets up for the firewall sidecar service.

Assisted-by: Claude Opus 4.6 via pi
change xrtnxnzwzuurswqzmlzktsxmkoynmwzy
commit 5aecadee4edfd61f794d87f6665fd633602857ff
author Alpha Chen <alpha@kejadlen.dev>
date
parent wkosntxr
diff --git a/compose.yml b/compose.yml
new file mode 100644
index 0000000..6d8b001
--- /dev/null
+++ b/compose.yml
@@ -0,0 +1,11 @@
+services:
+  agent:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    image: ramekin-agent
+    stdin_open: true
+    tty: true
+    volumes:
+      - "${RAMEKIN_WORKSPACE:-.}:/workspace"
+      - "${RAMEKIN_DATA_DIR}:/root/.pi"
diff --git a/src/main.rs b/src/main.rs
index 4b8ef5f..fe88fb6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,8 +14,6 @@ struct Cli {
     workspace: PathBuf,
 }
 
-const IMAGE_NAME: &str = "ramekin-agent";
-
 fn main() -> ExitCode {
     color_eyre::install().ok();
     tracing_subscriber::registry()
@@ -43,38 +41,48 @@ fn run() -> Result<()> {
     let pi_data_dir = xdg
         .create_data_directory("")
         .wrap_err("failed to create pi data directory")?;
+
     info!(data = %pi_data_dir.display(), "pi data directory");
+    info!(workspace = %workspace.display(), "starting agent");
 
-    let dockerfile = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Dockerfile");
+    let compose_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("compose.yml");
 
-    info!("building agent image");
-    let status = Command::new("docker")
-        .args(["build", "-t", IMAGE_NAME, "-f"])
-        .arg(&dockerfile)
-        .arg(dockerfile.parent().unwrap())
+    let docker_compose = |args: &[&str]| -> Result<Command> {
+        let mut cmd = Command::new("docker");
+        cmd.args(["compose", "-f"])
+            .arg(&compose_file)
+            .args(args)
+            .env("RAMEKIN_WORKSPACE", &workspace)
+            .env("RAMEKIN_DATA_DIR", &pi_data_dir);
+        Ok(cmd)
+    };
+
+    let status = docker_compose(&["up", "-d", "--build"])?
         .status()
-        .wrap_err("failed to run docker build")?;
+        .wrap_err("failed to run docker compose up")?;
 
     if !status.success() {
-        bail!("docker build failed ({})", status);
+        bail!("docker compose up failed ({})", status);
     }
 
-    info!(?workspace, "starting agent");
-    let status = Command::new("docker")
-        .args(["run", "--rm", "-it"])
-        .arg("-v")
-        .arg(format!("{}:/workspace", workspace.display()))
-        .arg("-v")
-        .arg(format!("{}:/root/.pi", pi_data_dir.display()))
-        .arg(IMAGE_NAME)
+    let status = docker_compose(&["attach", "agent"])?
         .stdin(Stdio::inherit())
         .stdout(Stdio::inherit())
         .stderr(Stdio::inherit())
         .status()
-        .wrap_err("failed to run docker")?;
+        .wrap_err("failed to attach to agent")?;
+
+    // Always tear down, regardless of attach exit status
+    let down_status = docker_compose(&["down"])?
+        .status()
+        .wrap_err("failed to run docker compose down")?;
+
+    if !down_status.success() {
+        error!("docker compose down failed ({})", down_status);
+    }
 
     if !status.success() {
-        bail!("docker run failed ({})", status);
+        bail!("agent exited with error ({})", status);
     }
 
     Ok(())