Skip to content

Commit 34c1d6b

Browse files
committed
refactor(volume): drop docker image seeding
1 parent 39ae0b8 commit 34c1d6b

12 files changed

Lines changed: 117 additions & 1527 deletions

File tree

docs/verity-volumes.md

Lines changed: 49 additions & 126 deletions
Large diffs are not rendered by default.

dstack/Cargo.lock

Lines changed: 9 additions & 218 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dstack/crates/dstack-cli/src/main.rs

Lines changed: 18 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -108,38 +108,24 @@ enum Command {
108108
},
109109
/// Scaffold a new app project in the current directory.
110110
Init,
111-
/// Build a verity volume that pre-loads docker images (or a directory) into
112-
/// a CVM.
111+
/// Build a read-only verity data volume from a directory or filesystem image.
113112
///
114-
/// The CVM mounts the volume instead of pulling and unpacking the images, so
115-
/// it starts in seconds. The build runs anywhere — no docker daemon, no TEE —
116-
/// but needs root, `mksquashfs`, and `veritysetup`. It prints a verity_root
117-
/// to paste into your compose. See docs/verity-volumes.md.
113+
/// The build needs no daemon or TEE. It prints a verity_root to paste into
114+
/// the deploy command. See docs/verity-volumes.md.
118115
Verity {
119-
/// images to bake in, ideally pinned by digest (e.g. `repo@sha256:...`).
120-
#[arg(value_name = "IMAGE")]
121-
images: Vec<String>,
122-
/// pack this directory into a data volume instead of images (mounted at a
123-
/// path you choose in the compose).
124-
#[arg(long, value_name = "PATH", conflicts_with = "images")]
116+
/// Pack this directory into a read-only data volume.
117+
#[arg(long, value_name = "PATH")]
125118
dir: Option<String>,
126119
/// wrap an existing filesystem image instead of building squashfs. The
127120
/// guest mounts it read-only after dm-verity verification.
128-
#[arg(long = "fs-image", value_name = "PATH", conflicts_with_all = ["images", "dir"])]
121+
#[arg(long = "fs-image", value_name = "PATH", conflicts_with = "dir")]
129122
fs_image: Option<String>,
130123
/// where to write the volume.
131124
#[arg(long, short = 'o', default_value = "verity.img")]
132125
output: String,
133126
/// squashfs compression: `none` (the default), `zstd`, or `gzip`.
134127
#[arg(long, default_value = "none")]
135128
compress: String,
136-
/// image platform to fetch; must match the guest. `linux/amd64` today,
137-
/// `linux/arm64` for arm64 hosts.
138-
#[arg(long, default_value = "linux/amd64")]
139-
platform: String,
140-
/// allow plain-HTTP registries (loopback registries already use HTTP).
141-
#[arg(long)]
142-
plain_http: bool,
143129
},
144130
}
145131

@@ -233,38 +219,28 @@ async fn main() -> Result<()> {
233219
Command::Info { .. } => stub("info"),
234220
Command::Init => stub("init"),
235221
Command::Verity {
236-
images,
237222
dir,
238223
fs_image,
239224
output,
240225
compress,
241-
platform,
242-
plain_http,
243226
} => {
244227
cmd_verity(
245-
&images,
246228
dir.as_deref(),
247229
fs_image.as_deref(),
248230
&output,
249231
&compress,
250-
&platform,
251-
plain_http,
252232
json,
253233
)
254234
.await
255235
}
256236
}
257237
}
258238

259-
#[allow(clippy::too_many_arguments)]
260239
async fn cmd_verity(
261-
images: &[String],
262240
dir: Option<&str>,
263241
fs_image: Option<&str>,
264242
output: &str,
265243
compress: &str,
266-
platform: &str,
267-
plain_http: bool,
268244
json: bool,
269245
) -> Result<()> {
270246
let compress = match compress {
@@ -274,13 +250,10 @@ async fn cmd_verity(
274250
other => bail!("unknown --compress '{other}' (use none|zstd|gzip)"),
275251
};
276252
let result = dstack_volume::verity(dstack_volume::VerityOptions {
277-
images: images.to_vec(),
278253
dir: dir.map(std::path::PathBuf::from),
279254
fs_image: fs_image.map(std::path::PathBuf::from),
280255
output: output.into(),
281256
compress,
282-
platform: platform.to_string(),
283-
plain_http,
284257
})
285258
.await?;
286259

@@ -289,58 +262,31 @@ async fn cmd_verity(
289262
.len();
290263

291264
if json {
292-
let imgs: Vec<_> = result
293-
.images
294-
.iter()
295-
.map(|i| {
296-
serde_json::json!({
297-
"reference": i.reference,
298-
"manifestDigest": i.manifest_digest,
299-
"configDigest": i.config_digest,
300-
"topChainId": i.top_chain_id,
301-
})
302-
})
303-
.collect();
304265
print_json(&serde_json::json!({
305266
"verityRoot": result.verity_root,
306267
"output": result.output.display().to_string(),
307268
"dataSize": result.data_size,
308269
"volumeSize": volume_size,
309-
"images": imgs,
310270
}));
311271
return Ok(());
312272
}
313273

314274
let mib = volume_size as f64 / 1_048_576.0;
315275
println!("wrote {} ({mib:.1} MiB)", result.output.display());
316-
if !result.images.is_empty() {
317-
// the manifest digest is what `image: repo@sha256:...` pins — not the
318-
// config digest (the image id), which can't be pulled by digest.
319-
println!("\nbaked images — pin each by digest in your compose:");
320-
for i in &result.images {
321-
println!(" {} @ {}", i.reference, i.manifest_digest);
322-
}
323-
}
324276
let file = result
325277
.output
326278
.file_name()
327279
.map(|s| s.to_string_lossy().into_owned())
328280
.unwrap_or_else(|| result.output.display().to_string());
329281
// a data volume mounts at a path you choose; it must be writable (the guest
330282
// rootfs is read-only), e.g. under /run.
331-
let target = if result.images.is_empty() {
332-
"/run/models"
333-
} else {
334-
"docker"
335-
};
283+
let target = "/run/models";
336284
println!("\ncopy {file} into the vmm's volumes_dir, then deploy with:");
337285
println!(
338286
" dstack deploy -c docker-compose.yaml --volume {file}:{}:{target}",
339287
result.verity_root
340288
);
341-
if result.images.is_empty() {
342-
println!(" (change {target} to your mount path)");
343-
}
289+
println!(" (change {target} to your mount path)");
344290
Ok(())
345291
}
346292

@@ -358,7 +304,7 @@ struct VolumeSpec {
358304
/// seeds content matching the attested root. `dstack verity` prints the exact
359305
/// spec to paste.
360306
///
361-
/// `TARGET` is `docker` (seed the image store) or an absolute mount path.
307+
/// `TARGET` is an absolute read-only mount path in the guest.
362308
fn parse_volume(spec: &str) -> Result<VolumeSpec> {
363309
let mut parts = spec.splitn(3, ':');
364310
let name = parts.next().unwrap_or_default();
@@ -377,8 +323,8 @@ fn parse_volume(spec: &str) -> Result<VolumeSpec> {
377323
if root.len() != 64 || !root.bytes().all(|b| b.is_ascii_hexdigit()) {
378324
bail!("verity_root '{root}' must be 64 hex chars (copy it from `dstack verity`)");
379325
}
380-
if target != "docker" && !target.starts_with('/') {
381-
bail!("target '{target}' must be \"docker\" or an absolute path");
326+
if !target.starts_with('/') {
327+
bail!("target '{target}' must be an absolute path");
382328
}
383329
Ok(VolumeSpec {
384330
// verity volumes are read-only by construction; a writable one would let a
@@ -728,19 +674,17 @@ mod tests {
728674
#[test]
729675
fn parses_volume_specs() {
730676
let root = "a".repeat(64);
731-
let docker = parse_volume(&format!("images.img:{root}:docker")).unwrap();
732-
assert_eq!(docker.volume.source, "images.img");
733-
assert!(docker.volume.read_only);
734-
assert_eq!(docker.verity_root, root);
735-
assert_eq!(docker.target, "docker");
736-
737677
let data = parse_volume(&format!("weights.img:{root}:/models/llama")).unwrap();
678+
assert_eq!(data.volume.source, "weights.img");
679+
assert!(data.volume.read_only);
680+
assert_eq!(data.verity_root, root);
738681
assert_eq!(data.target, "/models/llama");
739682

740683
assert!(parse_volume("weights.img").is_err()); // missing verity_root:target
741684
assert!(parse_volume(&format!("weights.img:{root}")).is_err()); // missing target
742-
assert!(parse_volume(&format!("../escape.img:{root}:docker")).is_err()); // path separator
743-
assert!(parse_volume("x.img:nothex:docker").is_err()); // verity_root not hex
685+
assert!(parse_volume(&format!("../escape.img:{root}:/models")).is_err()); // path separator
686+
assert!(parse_volume("x.img:nothex:/models").is_err()); // verity_root not hex
687+
assert!(parse_volume(&format!("x.img:{root}:docker")).is_err()); // docker seed removed
744688
assert!(parse_volume(&format!("x.img:{root}:relative/path")).is_err()); // bad target
745689
}
746690

@@ -782,13 +726,7 @@ mod tests {
782726
fn parses_verity_fs_image_flag() {
783727
let cli = Cli::parse_from(["dstack", "verity", "--fs-image", "rootfs.ext4"]);
784728
match cli.command {
785-
Command::Verity {
786-
images,
787-
dir,
788-
fs_image,
789-
..
790-
} => {
791-
assert!(images.is_empty());
729+
Command::Verity { dir, fs_image, .. } => {
792730
assert_eq!(dir, None);
793731
assert_eq!(fs_image.as_deref(), Some("rootfs.ext4"));
794732
}

dstack/crates/dstack-volume/Cargo.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,14 @@ binrw.workspace = true
1414
cmd_lib.workspace = true
1515
dstack-types.workspace = true
1616
tokio = { workspace = true, features = ["rt"] }
17-
serde = { workspace = true, features = ["derive", "std"] }
1817
serde_json = { workspace = true, features = ["std"] }
1918
sha2 = { workspace = true, features = ["std"] }
2019
hex = { workspace = true, features = ["std"] }
21-
flate2.workspace = true
22-
futures.workspace = true
2320
fs-err.workspace = true
2421
gpt = "4.1.0"
25-
tar.workspace = true
2622
tempfile.workspace = true
2723
tracing.workspace = true
2824
tracing-subscriber.workspace = true
29-
rustix = { version = "0.38", features = ["fs"] }
3025
uuid.workspace = true
31-
xattr = "1.5"
32-
oci-client = { version = "0.17.0", default-features = false, features = ["rustls-tls"] }
3326

3427
[dev-dependencies]

0 commit comments

Comments
 (0)