Skip to content

Commit ad3d758

Browse files
committed
Add isolated sandbox profiles and local submodule build hardening
1 parent f8758d8 commit ad3d758

20 files changed

Lines changed: 1548 additions & 184 deletions

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,43 @@ occ start --full-rebuild-sandbox-image
431431
- When the container fails to start due to image issues → try `--cached-rebuild-sandbox-image` first, then `--full-rebuild-sandbox-image`
432432
- When you want a completely fresh environment → use `--full-rebuild-sandbox-image`
433433
434+
**Local submodule dev rebuild (no push required):**
435+
```bash
436+
# Fast rebuild using local packages/opencode checkout (including uncommitted edits)
437+
just run start --cached-rebuild-sandbox-image --local-opencode-submodule
438+
439+
# Full no-cache rebuild from local packages/opencode checkout
440+
just run start --full-rebuild-sandbox-image --local-opencode-submodule
441+
```
442+
- This mode is for local development/debugging only and bypasses the default remote clone path.
443+
- `just run status` will show commit metadata derived from your local checkout (dirty trees are marked with `-dirty`).
444+
- Local context packaging intentionally skips heavyweight/dev metadata folders (for example `.planning`, `.git`, `node_modules`, `target`, and `dist`).
445+
- Keep CI/release workflows on the default pinned remote mode.
446+
447+
### Dockerfile Optimization Checklist
448+
449+
For new Docker build steps, follow this checklist:
450+
- Prefer BuildKit cache mounts (`RUN --mount=type=cache`) for package caches (`apt`, `bun`, `cargo`, `pip`, and `pnpm/npm`).
451+
- Create and remove temporary workdirs in the same `RUN` layer (for example `/tmp/opencode-repo`).
452+
- Do not defer cleanup to later layers; deleted files still exist in lower layers.
453+
- Keep builder-stage artifacts out of runtime layers by copying only final outputs.
454+
- When adding Docker build assets, update build-context inclusion logic in `packages/core/src/docker/image.rs`.
455+
- Keep local submodule exclude lists aligned with Dockerfile hygiene rules (`.planning`, `.git`, `node_modules`, `target`, `dist`, and similar dev metadata).
456+
457+
This policy is intentional for both image-size hygiene and fast local rebuilds.
458+
459+
**Worktree-isolated sandbox profiles (opt-in):**
460+
```bash
461+
# Derive a stable instance name from the current git worktree root
462+
just run --sandbox-instance auto start --cached-rebuild-sandbox-image
463+
464+
# Use an explicit instance name
465+
just run --sandbox-instance mytree start --cached-rebuild-sandbox-image
466+
```
467+
- Default behavior (no `--sandbox-instance`) remains the shared legacy sandbox.
468+
- Isolated instances use separate container names, image tags, Docker volumes, and image-state files.
469+
- You can also set `OPENCODE_SANDBOX_INSTANCE=<name|auto>` instead of passing the CLI flag every time.
470+
434471
## Configuration
435472
436473
Configuration is stored at:

packages/cli-rust/src/commands/logs.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use opencode_cloud_core::bollard::container::LogOutput;
1111
use opencode_cloud_core::bollard::exec::{CreateExecOptions, StartExecOptions, StartExecResults};
1212
use opencode_cloud_core::bollard::query_parameters::LogsOptions;
1313
use opencode_cloud_core::docker::{
14-
CONTAINER_NAME, DockerClient, container_is_running, exec_command_exit_code,
14+
DockerClient, active_resource_names, container_is_running, exec_command_exit_code,
1515
};
1616

1717
/// Arguments for the logs command
@@ -38,6 +38,10 @@ pub struct LogsArgs {
3838
pub broker: bool,
3939
}
4040

41+
fn active_container_name() -> String {
42+
active_resource_names().container_name
43+
}
44+
4145
/// Stream logs from the opencode container
4246
///
4347
/// By default, shows the last 50 lines and follows new output.
@@ -46,6 +50,7 @@ pub struct LogsArgs {
4650
///
4751
/// In quiet mode, outputs raw lines without status messages or colors.
4852
pub async fn cmd_logs(args: &LogsArgs, maybe_host: Option<&str>, quiet: bool) -> Result<()> {
53+
let container_name = active_container_name();
4954
// Resolve Docker client (local or remote)
5055
let (client, host_name) = crate::resolve_docker_client(maybe_host).await?;
5156

@@ -62,7 +67,10 @@ pub async fn cmd_logs(args: &LogsArgs, maybe_host: Option<&str>, quiet: bool) ->
6267
.map_err(|e| format_docker_error_anyhow(&e))?;
6368

6469
// Check if container exists
65-
let inspect_result = client.inner().inspect_container(CONTAINER_NAME, None).await;
70+
let inspect_result = client
71+
.inner()
72+
.inspect_container(&container_name, None)
73+
.await;
6674

6775
match inspect_result {
6876
Err(opencode_cloud_core::bollard::errors::Error::DockerResponseServerError {
@@ -112,7 +120,7 @@ pub async fn cmd_logs(args: &LogsArgs, maybe_host: Option<&str>, quiet: bool) ->
112120
};
113121

114122
// Get log stream
115-
let mut stream = client.inner().logs(CONTAINER_NAME, Some(options));
123+
let mut stream = client.inner().logs(&container_name, Some(options));
116124

117125
// Process log stream
118126
while let Some(result) = stream.next().await {
@@ -125,7 +133,7 @@ pub async fn cmd_logs(args: &LogsArgs, maybe_host: Option<&str>, quiet: bool) ->
125133
Err(_) => {
126134
// Stream error - check if container stopped
127135
if follow
128-
&& !container_is_running(&client, CONTAINER_NAME)
136+
&& !container_is_running(&client, &container_name)
129137
.await
130138
.unwrap_or(false)
131139
&& !quiet
@@ -166,9 +174,10 @@ async fn stream_broker_logs(
166174
}
167175

168176
async fn ensure_systemd_available(client: &DockerClient) -> Result<bool> {
177+
let container_name = active_container_name();
169178
let systemd_available = exec_command_exit_code(
170179
client,
171-
CONTAINER_NAME,
180+
&container_name,
172181
vec!["test", "-d", "/run/systemd/system"],
173182
)
174183
.await
@@ -215,6 +224,7 @@ fn build_broker_journalctl_command(args: &LogsArgs) -> Result<Vec<String>> {
215224
}
216225

217226
async fn create_broker_exec(client: &DockerClient, cmd: Vec<String>) -> Result<String> {
227+
let container_name = active_container_name();
218228
let exec_config = CreateExecOptions {
219229
attach_stdout: Some(true),
220230
attach_stderr: Some(true),
@@ -225,7 +235,7 @@ async fn create_broker_exec(client: &DockerClient, cmd: Vec<String>) -> Result<S
225235

226236
let exec = client
227237
.inner()
228-
.create_exec(CONTAINER_NAME, exec_config)
238+
.create_exec(&container_name, exec_config)
229239
.await
230240
.map_err(|e| anyhow!("Failed to create exec for broker logs: {e}"))?;
231241

@@ -239,6 +249,7 @@ async fn stream_broker_exec_output(
239249
line_prefix: Option<&str>,
240250
quiet: bool,
241251
) -> Result<()> {
252+
let container_name = active_container_name();
242253
let start_config = StartExecOptions {
243254
detach: false,
244255
..Default::default()
@@ -262,7 +273,7 @@ async fn stream_broker_exec_output(
262273
}
263274
Err(_) => {
264275
if !args.no_follow
265-
&& !container_is_running(client, CONTAINER_NAME)
276+
&& !container_is_running(client, &container_name)
266277
.await
267278
.unwrap_or(false)
268279
&& !quiet
@@ -291,6 +302,7 @@ async fn stream_broker_logs_from_container(
291302
line_prefix: Option<&str>,
292303
quiet: bool,
293304
) -> Result<()> {
305+
let container_name = active_container_name();
294306
let follow = !args.no_follow;
295307
let options = LogsOptions {
296308
stdout: true,
@@ -301,7 +313,7 @@ async fn stream_broker_logs_from_container(
301313
..Default::default()
302314
};
303315

304-
let mut stream = client.inner().logs(CONTAINER_NAME, Some(options));
316+
let mut stream = client.inner().logs(&container_name, Some(options));
305317

306318
while let Some(result) = stream.next().await {
307319
match result {
@@ -315,7 +327,7 @@ async fn stream_broker_logs_from_container(
315327
}
316328
Err(_) => {
317329
if follow
318-
&& !container_is_running(client, CONTAINER_NAME)
330+
&& !container_is_running(client, &container_name)
319331
.await
320332
.unwrap_or(false)
321333
&& !quiet

0 commit comments

Comments
 (0)