Skip to content

Feature request: support build.target in coast build for Docker Compose services #76

@artburkart

Description

@artburkart

Summary

Please add support for more of Docker Compose's build: options during coast build, especially:

  • build.target

Right now, the implementation appears to intentionally preserve only context and dockerfile when building host-side images for compose services.

I would be willing to implement this if the maintainers think it fits the project direction.

Why this matters

For dev workflows, it is not uncommon to use a Dockerfile with:

  • a lightweight dev stage selected via build.target
  • a later production-only stage that should not run in normal local builds

Plain docker compose build handles this correctly. coast build currently does not.

That makes Coast harder to adopt for projects that already have a clean multi-stage Compose-based dev/prod setup.

Example

Compose service:

services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
      target: compose-dev

Dockerfile shape:

FROM python:3.13-slim AS builder
# ...

FROM runtime-base AS compose-dev

FROM debian:bookworm-slim AS asset-fetch
ARG ASSET_REF=main

Desired behavior:

  • coast build should honor target: compose-dev

Current implementation

The current implementation explicitly ignores target, args, and similar build: fields.

  • ComposeBuildDirective only stores context and dockerfile
    pub struct ComposeBuildDirective {
    /// The compose service name (e.g., "app").
    pub service_name: String,
    /// Build context path, relative to the compose file directory.
    pub context: String,
    /// Optional Dockerfile path (relative to context).
    pub dockerfile: Option<String>,
    /// The coast-built image tag (e.g., "coast-built/my-project/app:latest").
    pub coast_image_tag: String,
    }
  • parse_compose_file_inner(...) only extracts context and dockerfile
    if has_build {
    let build_val = value.get("build").unwrap();
    let (context, dockerfile) = match build_val {
    serde_yaml::Value::String(s) => (s.clone(), None),
    serde_yaml::Value::Mapping(m) => {
    let ctx = m
    .get(serde_yaml::Value::String("context".to_string()))
    .and_then(|v| v.as_str())
    .unwrap_or(".")
    .to_string();
    let df = m
    .get(serde_yaml::Value::String("dockerfile".to_string()))
    .and_then(|v| v.as_str())
    .map(std::string::ToString::to_string);
    (ctx, df)
    }
    _ => (".".to_string(), None),
    };
    build_directives.push(ComposeBuildDirective {
    service_name: service_name.clone(),
    context,
    dockerfile,
    coast_image_tag: coast_built_image_tag(project, &service_name),
    });
  • docker_build_cmd(...) emits docker build without --target
    /// Construct the `docker build` command for a build directive.
    ///
    /// Returns the command as a vector of strings suitable for `tokio::process::Command`.
    pub fn docker_build_cmd(directive: &ComposeBuildDirective, compose_dir: &Path) -> Vec<String> {
    let mut cmd = vec![
    "docker".to_string(),
    "build".to_string(),
    "-t".to_string(),
    directive.coast_image_tag.clone(),
    ];
    if let Some(ref df) = directive.dockerfile {
    cmd.push("-f".to_string());
    cmd.push(
    compose_dir
    .join(&directive.context)
    .join(df)
    .display()
    .to_string(),
    );
    }
    cmd.push(compose_dir.join(&directive.context).display().to_string());
    cmd
  • Test comment explicitly stating that args, target, etc. are ignored
    #[test]
    fn test_parse_build_with_extra_fields() {
    // build: can have args, target, etc. — we only care about context and dockerfile
    let yaml = r#"
    services:
    app:
    build:
    context: .
    dockerfile: Dockerfile
    args:
    NODE_ENV: production
    target: builder
    "#;
    let result = parse_compose_file(yaml, "proj").unwrap();
    assert_eq!(result.build_directives.len(), 1);
    assert_eq!(result.build_directives[0].context, ".");
    assert_eq!(

Implementation note

I recognize there are other build arguments that would be valuable to have, such as --build-arg. There's a Rust compose_spec crate that might be useful for processing arguments more generically. Though I'm not sure it would be preferred over picking out fields individually. Limiting the scope today probably makes it easier to debug usage issues.

Requested capability

  1. Support compose build.target by passing it through to docker build --target.
  2. Consider supporting other useful compose build fields as well, or document the intentionally supported subset clearly.

Environment

  • Coasts: 0.1.24
  • macOS
  • Docker Desktop

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions