diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..b273fcd206 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +**/target* +# ignore edits to dockerfiles so they don't invalidate cache +# dockerfiles shouldn't be in the build image anyway +**/docker/Dockerfile* \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dc308f8647..0d8f52726c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,4 +33,11 @@ inherits = 'release' lto = true panic = 'abort' incremental = false +codegen-units = 8 + +[profile.docker] +inherits = 'release' +lto = true +panic = 'abort' +incremental = false codegen-units = 8 \ No newline at end of file diff --git a/docker/Dockerfile.iroh-gateway b/docker/Dockerfile.iroh-gateway new file mode 100644 index 0000000000..20d7a972b5 --- /dev/null +++ b/docker/Dockerfile.iroh-gateway @@ -0,0 +1,46 @@ +################################################################################ +## Builder +################################################################################ +FROM rust:latest AS builder + +RUN update-ca-certificates + +# install latest protocol buffer compiler. +ARG TARGETPLATFORM +COPY ../docker/install_protoc.sh . +RUN ./install_protoc.sh + +# set build env vars +ENV RUST_BACKTRACE=1 \ + PROTOC=/usr/local/bin/protoc \ + PROTOC_INCLUDE=/usr/local/include + +# has the side effect of updating the crates.io index & installing rust toolchain +# called in a separate step for nicer caching. the command itself will fail, +# b/c empty-library is not a dependency, so we override with an exit code 0 +RUN cargo install empty-library; exit 0 + +WORKDIR /iroh + +COPY ../ . + +RUN cargo build --bin iroh-gateway --profile=docker + +################################################################################ +## Final image +################################################################################ +FROM gcr.io/distroless/cc + +WORKDIR /iroh + +# Copy our build, changing owndership to distroless-provided "nonroot" user, +# (65532:65532) +COPY --from=builder --chown=65532:65532 /iroh/target/docker/iroh-gateway ./ + +# Use nonroot (unprivileged) user +USER nonroot + +# expose the default RPC port and default gateway HTTP port +EXPOSE 4400 9050 + +CMD ["/iroh/iroh-gateway"] \ No newline at end of file diff --git a/docker/Dockerfile.iroh-one b/docker/Dockerfile.iroh-one new file mode 100644 index 0000000000..a82aefc59d --- /dev/null +++ b/docker/Dockerfile.iroh-one @@ -0,0 +1,52 @@ +################################################################################ +## Builder +################################################################################ +# FROM --platform=linux/amd64 rust:latest AS builder +FROM rust:latest AS builder + +RUN update-ca-certificates + +# rocksDB needs libclang +RUN apt-get update && \ + apt-get install -y \ + clang libclang-dev + +# install latest protocol buffer compiler. +ARG TARGETPLATFORM +COPY ../docker/install_protoc.sh . +RUN ./install_protoc.sh + +# set build env vars +ENV RUST_BACKTRACE=1 \ + PROTOC=/usr/local/bin/protoc \ + PROTOC_INCLUDE=/usr/local/include + +# has the side effect of updating the crates.io index & installing rust toolchain +# called in a separate step for nicer caching. the command itself will fail, +# b/c empty-library is not a dependency, so we override with an exit code 0 +RUN cargo install empty-library; exit 0 + +WORKDIR /iroh + +COPY ../ . + +RUN cargo build --bin iroh-one --profile=docker + +################################################################################ +## Final image +################################################################################ +FROM gcr.io/distroless/cc + +WORKDIR /iroh + +# Copy our build, changing owndership to distroless-provided "nonroot" user, +# (65532:65532) +COPY --from=builder --chown=65532:65532 /iroh/target/docker/iroh-one ./ + +# Use nonroot (unprivileged) user +USER nonroot + +# expose gateway, p2p & all default RPC ports +EXPOSE 4400 4401 4402 4403 4444 9050 + +CMD ["/iroh/iroh-one"] \ No newline at end of file diff --git a/docker/Dockerfile.iroh-p2p b/docker/Dockerfile.iroh-p2p new file mode 100644 index 0000000000..e04b31abbb --- /dev/null +++ b/docker/Dockerfile.iroh-p2p @@ -0,0 +1,53 @@ +################################################################################ +## Builder +################################################################################ +FROM rust:latest AS builder + +RUN update-ca-certificates + +# install latest protocol buffer compiler. +ARG TARGETPLATFORM +COPY ../docker/install_protoc.sh . +RUN ./install_protoc.sh + +# set build env vars +ENV RUST_BACKTRACE=1 \ + PROTOC=/usr/local/bin/protoc \ + PROTOC_INCLUDE=/usr/local/include + +# has the side effect of updating the crates.io index & installing rust toolchain +# called in a separate step for nicer caching. the command itself will fail, +# b/c empty-library is not a dependency, so we override with an exit code 0 +RUN cargo install empty-library; exit 0 + +WORKDIR /iroh + +COPY ../ . + +RUN cargo build --bin iroh-p2p --profile=docker + +################################################################################ +## Final image +################################################################################ +FROM gcr.io/distroless/cc + +WORKDIR /iroh + +# Copy our build, changing owndership to distroless-provided "nonroot" user, +# (65532:65532) +COPY --from=builder --chown=65532:65532 /iroh/target/docker/iroh-p2p ./ + +# TODO (b5) - investigate max file descriptor limits within the container image +# libp2p needs lots of FDs for open ports, and we should be maxing them out. +# I have no idea if distroless honors ERL_MAX_PORTS, consider this a starting +# point for experimentation +# ENV ERL_MAX_PORTS=65536 + +# Use nonroot (unprivileged) user +USER nonroot + +# expose the default RPC port +EXPOSE 4401 4444 +EXPOSE 4444/udp + +CMD ["/iroh/iroh-p2p"] \ No newline at end of file diff --git a/docker/Dockerfile.iroh-store b/docker/Dockerfile.iroh-store new file mode 100644 index 0000000000..c8e81f19dd --- /dev/null +++ b/docker/Dockerfile.iroh-store @@ -0,0 +1,51 @@ +################################################################################ +## Builder +################################################################################ +FROM rust:latest AS builder + +RUN update-ca-certificates + +# rocksDB needs libclang +RUN apt-get update && \ + apt-get install -y \ + clang libclang-dev + +# install latest protocol buffer compiler. +ARG TARGETPLATFORM +COPY ../docker/install_protoc.sh . +RUN ./install_protoc.sh + +# set build env vars +ENV RUST_BACKTRACE=1 \ + PROTOC=/usr/local/bin/protoc \ + PROTOC_INCLUDE=/usr/local/include + +# has the side effect of updating the crates.io index & installing rust toolchain +# called in a separate step for nicer caching. the command itself will fail, +# b/c empty-library is not a dependency, so we override with an exit code 0 +RUN cargo install empty-library; exit 0 + +WORKDIR /iroh + +COPY ../ . + +RUN cargo build --bin iroh-store --profile=docker + +################################################################################ +## Final image +################################################################################ +FROM gcr.io/distroless/cc + +WORKDIR /iroh + +# Copy our build, changing owndership to distroless-provided "nonroot" user, +# (65532:65532) +COPY --from=builder --chown=65532:65532 /iroh/target/docker/iroh-store ./ + +# Use nonroot (unprivileged) user +USER nonroot + +# expose the default RPC port +EXPOSE 4402 + +CMD ["/iroh/iroh-store"] \ No newline at end of file diff --git a/docker/install_protoc.sh b/docker/install_protoc.sh new file mode 100755 index 0000000000..6e180972d7 --- /dev/null +++ b/docker/install_protoc.sh @@ -0,0 +1,14 @@ +# install latest protocol buffer compiler. Yes, it's really this irritating. +# recent build URLs are missing "3" version prefix. version is actually "3.21.9" +PROTOC_VERSION=21.9 +case ${TARGETPLATFORM} in + "linux/amd64") PROTOC_ZIP=protoc-21.9-linux-x86_64.zip ;; + "linux/arm64") PROTOC_ZIP=protoc-21.9-linux-aarch_64.zip ;; + *) exit 1 +esac + +curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP +unzip -o $PROTOC_ZIP -d /usr/local bin/protoc +unzip -o $PROTOC_ZIP -d /usr/local 'include/*' +rm -f $PROTOC_ZIP +echo "installed $($PROTOC --version)" diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1a16ae18f5..bdf7c2bdc1 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -4,6 +4,7 @@ use std::{ env, fs, io, path::{Path, PathBuf}, process::Command, + str, }; #[derive(Debug, Parser)] @@ -26,6 +27,15 @@ enum Commands { #[clap(short, long)] build: bool, }, + #[command(about = "build docker images")] + Docker { + #[clap(short, long)] + all: bool, + /// Set type of progress output (auto, plain, tty). Use plain to show container output (default "auto") + #[clap(short, long, default_value_t = String::from("auto"))] + progress: String, + images: Vec, + }, } fn main() { @@ -41,6 +51,11 @@ fn run_subcommand(args: Cli) -> Result<()> { Commands::Dist {} => dist()?, Commands::Man {} => dist_manpage()?, Commands::DevInstall { build } => dev_install(build)?, + Commands::Docker { + all, + images, + progress, + } => build_docker(all, images, progress)?, } Ok(()) } @@ -130,3 +145,50 @@ fn project_root() -> PathBuf { fn dist_dir() -> PathBuf { project_root().join("target/dist") } + +fn build_docker(all: bool, build_images: Vec, progress: String) -> Result<()> { + let mut images = build_images; + if all { + images = vec![ + String::from("iroh-one"), + String::from("iroh-store"), + String::from("iroh-p2p"), + String::from("iroh-gateway"), + ]; + } + + let commit = current_git_commit()?; + + for image in images { + println!("building {}:{}", image, commit); + let status = Command::new("docker") + .current_dir(project_root()) + .args([ + "build", + "-t", + format!("n0computer/{}:{}", image, commit).as_str(), + "-t", + format!("n0computer/{}:latest", image).as_str(), + "-f", + format!("docker/Dockerfile.{}", image).as_str(), + format!("--progress={}", progress).as_str(), + ".", + ]) + .status()?; + + if !status.success() { + Err(anyhow::anyhow!("cargo build failed"))?; + } + } + + Ok(()) +} + +fn current_git_commit() -> Result { + let output = Command::new("git") + .current_dir(project_root()) + .args(["log", "-1", "--pretty=%h"]) + .output()?; + let commitish = str::from_utf8(&output.stdout)?.trim_end(); + Ok(String::from(commitish)) +}