Skip to content

Commit 3c04294

Browse files
committed
feat: shuttle build, shuttle build --docker, shuttle run --docker
1 parent 933cbe9 commit 3c04294

File tree

13 files changed

+648
-97
lines changed

13 files changed

+648
-97
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ resolver = "2"
33
members = [
44
"admin",
55
"api-client",
6+
"builder",
67
"cargo-shuttle",
78
"codegen",
89
"common",
@@ -21,13 +22,15 @@ repository = "https://github.com/shuttle-hq/shuttle"
2122

2223
[workspace.dependencies]
2324
shuttle-api-client = { path = "api-client", version = "0.56.1", default-features = false }
25+
shuttle-builder = { path = "builder", version = "0.56.0" }
2426
shuttle-codegen = { path = "codegen", version = "0.56.0" }
2527
shuttle-common = { path = "common", version = "0.56.0" }
2628
shuttle-ifc = { path = "ifc", version = "0.56.0" }
2729
shuttle-mcp = { path = "mcp", version = "0.56.0" }
2830
shuttle-service = { path = "service", version = "0.56.0" }
2931

3032
anyhow = "1.0.66"
33+
askama = "0.14.0"
3134
assert_cmd = "2.0.6"
3235
async-trait = "0.1.58"
3336
axum = { version = "0.8.1", default-features = false }

builder/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "shuttle-builder"
3+
version = "0.56.0"
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
description = "Docker build recipes for the Shuttle platform (shuttle.dev)"
8+
homepage = "https://www.shuttle.dev"
9+
10+
[dependencies]
11+
shuttle-common = { workspace = true, features = ["models"] }
12+
13+
askama = { workspace = true }
14+
15+
[dev-dependencies]
16+
pretty_assertions = { workspace = true }

builder/src/lib.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use askama::Template;
2+
use shuttle_common::models::deployment::BuildArgsRust;
3+
4+
#[derive(Template)]
5+
#[template(path = "rust.Dockerfile.jinja2", escape = "none")]
6+
pub struct RustDockerfile<'a> {
7+
/// local or remote image name for the chef image
8+
pub chef_image: &'a str,
9+
/// content of inlined chef dockerfile
10+
pub cargo_chef_dockerfile: Option<&'a str>,
11+
/// local or remote image name for the runtime image
12+
pub runtime_image: &'a str,
13+
/// content of inlined runtime dockerfile
14+
pub runtime_base_dockerfile: Option<&'a str>,
15+
pub build_args: &'a BuildArgsRust,
16+
}
17+
18+
pub fn render_rust_dockerfile(build_args: &BuildArgsRust) -> String {
19+
RustDockerfile {
20+
chef_image: "cargo-chef",
21+
cargo_chef_dockerfile: Some(include_str!("../templates/cargo-chef.Dockerfile")),
22+
runtime_image: "runtime-base",
23+
runtime_base_dockerfile: Some(include_str!("../templates/runtime-base.Dockerfile")),
24+
build_args,
25+
}
26+
.render()
27+
.unwrap()
28+
}
29+
30+
#[cfg(test)]
31+
mod tests {
32+
use super::*;
33+
use pretty_assertions::assert_str_eq;
34+
35+
#[test]
36+
fn rust_basic() {
37+
let t = RustDockerfile {
38+
chef_image: "chef",
39+
cargo_chef_dockerfile: Some("foo"),
40+
runtime_image: "rt",
41+
runtime_base_dockerfile: Some("bar"),
42+
build_args: &BuildArgsRust {
43+
package_name: Some("hello".into()),
44+
features: Some("asdf".into()),
45+
..Default::default()
46+
},
47+
};
48+
49+
let s = t.render().unwrap();
50+
51+
assert!(s.contains("foo\n\n"));
52+
assert!(s.contains("bar\n\n"));
53+
assert!(s.contains("FROM chef AS chef"));
54+
assert!(s.contains("FROM rt AS runtime"));
55+
assert!(s.contains("RUN cargo chef cook --release --package hello --features asdf\n"));
56+
assert!(s.contains("mv /app/target/release/hello"));
57+
}
58+
59+
#[test]
60+
fn rust_full() {
61+
let s = render_rust_dockerfile(&BuildArgsRust {
62+
package_name: Some("hello".into()),
63+
features: Some("asdf".into()),
64+
..Default::default()
65+
});
66+
assert_str_eq!(s, include_str!("../tests/rust.Dockerfile"));
67+
}
68+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#syntax=docker/dockerfile:1.4
2+
3+
FROM lukemathwalker/cargo-chef:latest AS cargo-chef
4+
5+
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
6+
7+
RUN <<EOT
8+
# Files and directories used by the Shuttle build process:
9+
mkdir /build_assets
10+
mkdir /app
11+
# Create empty files in place for optional user scripts, etc.
12+
# Having them empty means we can skip checking for them with [ -f ... ] etc.
13+
touch /app/Shuttle.toml
14+
touch /app/shuttle_prebuild.sh
15+
touch /app/shuttle_postbuild.sh
16+
touch /app/shuttle_setup_container.sh
17+
EOT
18+
19+
# Install common build tools for external crates
20+
# The image should already have these: https://github.com/docker-library/buildpack-deps/blob/fdfe65ea0743aa735b4a5f27cac8e281e43508f5/debian/bookworm/Dockerfile
21+
RUN <<EOT
22+
apt-get update
23+
24+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
25+
clang \
26+
cmake \
27+
jq \
28+
llvm-dev \
29+
libclang-dev \
30+
mold \
31+
protobuf-compiler
32+
33+
apt-get clean
34+
rm -rf /var/lib/apt/lists/*
35+
EOT
36+
37+
# Add the wasm32 target for building frontend frameworks
38+
RUN rustup target add wasm32-unknown-unknown
39+
40+
# cargo binstall
41+
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
42+
43+
# Utility tools for build process
44+
RUN cargo binstall -y --locked convert2json@1.1.5
45+
46+
# Common cargo build tools (for the user to use)
47+
RUN cargo binstall -y --locked trunk@0.21.7
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#syntax=docker/dockerfile:1.4
2+
3+
FROM debian:bookworm-slim AS runtime-base
4+
5+
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
6+
7+
# ca-certificates for native-tls, curl for health check
8+
RUN <<EOT
9+
apt-get update
10+
11+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
12+
ca-certificates \
13+
curl
14+
15+
apt-get clean
16+
rm -rf /var/lib/apt/lists/*
17+
EOT
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{% if let Some(s) = cargo_chef_dockerfile %}{{s}}{% endif %}
2+
3+
{% if let Some(s) = runtime_base_dockerfile %}{{s}}{% endif %}
4+
5+
FROM {{ chef_image }} AS chef
6+
WORKDIR /app
7+
ENV SHUTTLE=true
8+
9+
10+
{% if build_args.cargo_chef %}
11+
FROM chef AS planner
12+
COPY . .
13+
RUN cargo chef prepare
14+
{% endif %}
15+
16+
17+
FROM chef AS builder
18+
19+
COPY shuttle_prebuild.sh .
20+
RUN bash shuttle_prebuild.sh
21+
22+
{% if build_args.mold %}
23+
export RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold"
24+
{% endif %}
25+
26+
{% if build_args.cargo_chef %}
27+
COPY --from=planner /app/recipe.json recipe.json
28+
RUN cargo chef cook --release
29+
{%- if let Some(s) = build_args.package_name %} --package {{s}}{% endif %}
30+
{%- if let Some(s) = build_args.binary_name %} --bin {{s}}{% endif %}
31+
{%- if let Some(s) = build_args.features %} --features {{s}}{% endif %}
32+
{%- if build_args.no_default_features %} --no-default-features{% endif %}
33+
{% endif %}
34+
35+
COPY . .
36+
37+
{% if build_args.cargo_build %}
38+
RUN cargo build --release
39+
{%- if let Some(s) = build_args.package_name %} --package {{s}}{% endif %}
40+
{%- if let Some(s) = build_args.binary_name %} --bin {{s}}{% endif %}
41+
{%- if let Some(s) = build_args.features %} --features {{s}}{% endif %}
42+
{%- if build_args.no_default_features %} --no-default-features{% endif %}
43+
{% endif %}
44+
45+
RUN bash shuttle_postbuild.sh
46+
47+
RUN mv /app/target/release/
48+
{%- if let Some(s) = build_args.binary_name -%}
49+
{{s}}
50+
{%- else if let Some(s) = build_args.package_name -%}
51+
{{s}}
52+
{%- endif %} /executable
53+
54+
{# Create folders and copy paths of all specified build assets #}
55+
{# For loop is used so that no find command is run when the array is empty #}
56+
RUN for path in $(tq -r '.build.assets // .build_assets // [] | join(" ")' Shuttle.toml); do find "$path" -type f -exec echo Copying \{\} \; -exec install -D \{\} /build_assets/\{\} \; ; done
57+
58+
59+
FROM {{ runtime_image }} AS runtime
60+
WORKDIR /app
61+
62+
COPY --from=builder /app/shuttle_setup_container.sh /tmp
63+
RUN bash /tmp/shuttle_setup_container.sh; rm /tmp/shuttle_setup_container.sh
64+
65+
COPY --from=builder /build_assets /app
66+
COPY --from=builder /executable /usr/local/bin/runtime
67+
68+
ENTRYPOINT ["/usr/local/bin/runtime"]
69+

0 commit comments

Comments
 (0)