From f2fc86fdd96b06a26362e70cf6a176e79f4e43ce Mon Sep 17 00:00:00 2001 From: nully0x <40327060+nully0x@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:56:41 +0100 Subject: [PATCH] Optimize CI/CD: Implement Nix-based dev builds (#28) * chore: implement nix base container image build * chore: switch dev workflow to nix base image build --- .github/workflows/docker-publish.yml | 120 +++++++++++++++------------ build-image.nix | 81 ++++++++++++++++++ 2 files changed, 147 insertions(+), 54 deletions(-) create mode 100644 build-image.nix diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 96e692b..2f79e24 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,8 +1,8 @@ -name: Docker Publish +name: Docker Build and Publish on: push: - branches: [ "main" ] + branches: ["main"] release: types: [created] @@ -13,59 +13,82 @@ env: CARGO_TERM_COLOR: always jobs: - build-and-push: + build-and-push-dev: + if: github.event_name == 'push' runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 + - name: Install Nix + uses: cachix/install-nix-action@v20 with: - profile: minimal - toolchain: stable + nix_path: nixpkgs=channel:nixos-unstable + extra_nix_config: | + experimental-features = nix-command flakes + accept-flake-config = true + keep-outputs = true + keep-derivations = true - - name: Cache cargo registry + - name: Setup Nix caching uses: actions/cache@v3 with: path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + ~/.cache/nix + key: ${{ runner.os }}-nix-${{ hashFiles('**/docker-image.nix', '**/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-cargo- + ${{ runner.os }}-nix- - - name: Install cargo-chef - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-chef + - name: Build with Nix + run: | + nix-build build-image.nix + docker load < result - - name: Prepare recipe - run: cargo chef prepare --recipe-path recipe.json + - name: Verify image contents + run: | + docker run --rm hxckr-core:latest ls -l /app + docker run --rm hxckr-core:latest ls -l /app/migrations + docker run --rm hxckr-core:latest cat /app/entrypoint.sh + docker run --rm hxckr-core:latest which diesel + docker run --rm hxckr-core:latest diesel --version - - name: Cache dependencies - uses: actions/cache@v3 + - name: Login to DockerHub + uses: docker/login-action@v2 with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-chef-${{ hashFiles('**/recipe.json') }} - restore-keys: | - ${{ runner.os }}-cargo-chef- + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ env.DOCKERHUB_TOKEN }} - - name: Build dependencies - run: cargo chef cook --release --recipe-path recipe.json + - name: Push Docker image + run: | + docker tag hxckr-core:latest ${{ env.IMAGE_NAME }}:dev + docker tag hxckr-core:latest ${{ env.IMAGE_NAME }}:${{ github.sha }} + docker push ${{ env.IMAGE_NAME }}:dev + docker push ${{ env.IMAGE_NAME }}:${{ github.sha }} - - name: Build project - run: cargo build --release --all-features + - name: Print image size and details + run: | + docker image ls ${{ env.IMAGE_NAME }}:dev + docker history ${{ env.IMAGE_NAME }}:dev + + build-and-push-prod: + if: github.event_name == 'release' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + - name: Setup Rust + uses: actions-rs/toolchain@v1 with: - install: true + profile: minimal + toolchain: stable + + - name: Build project + run: | + cargo install cargo-chef + cargo chef prepare --recipe-path recipe.json + cargo chef cook --release --recipe-path recipe.json + cargo build --release --all-features - name: Login to DockerHub uses: docker/login-action@v2 @@ -73,22 +96,7 @@ jobs: username: ${{ env.DOCKERHUB_USERNAME }} password: ${{ env.DOCKERHUB_TOKEN }} - - name: Build and push Docker image (Development) - if: github.event_name == 'push' - uses: docker/build-push-action@v4 - with: - context: . - file: Dockerfile.dev - platforms: linux/amd64,linux/arm64 - push: true - tags: | - ${{ env.IMAGE_NAME }}:dev - ${{ env.IMAGE_NAME }}:${{ github.sha }} - cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache - cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max - - - name: Build and push Docker image (Production) - if: github.event_name == 'release' + - name: Build and push Docker image uses: docker/build-push-action@v4 with: context: . @@ -101,8 +109,7 @@ jobs: cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max - - name: Run migrations (Production) - if: github.event_name == 'release' + - name: Run migrations env: DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }} run: | @@ -110,3 +117,8 @@ jobs: -e DATABASE_URL \ ${{ env.IMAGE_NAME }}:${{ github.ref_name }} \ diesel migration run + + - name: Print image size and details + run: | + docker image ls ${{ env.IMAGE_NAME }}:${{ github.ref_name }} + docker history ${{ env.IMAGE_NAME }}:${{ github.ref_name }} diff --git a/build-image.nix b/build-image.nix new file mode 100644 index 0000000..7216d71 --- /dev/null +++ b/build-image.nix @@ -0,0 +1,81 @@ +{ pkgs ? import {} }: + +let + rustOverlay = import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"); + pkgs = import { overlays = [ rustOverlay ]; }; + rust = pkgs.rust-bin.stable."1.80.0".default; + + hxckr-core = pkgs.rustPlatform.buildRustPackage { + pname = "hxckr-core"; + version = "0.1.0"; + src = ./.; + cargoLock.lockFile = ./Cargo.lock; + + nativeBuildInputs = [ pkgs.pkg-config ]; + buildInputs = [ pkgs.openssl pkgs.postgresql ]; + + doCheck = false; + + # Optimize the build + RUSTFLAGS = "-C target-cpu=native -C opt-level=3"; + CARGO_PROFILE_RELEASE_LTO = "thin"; + CARGO_PROFILE_RELEASE_CODEGEN_UNITS = "16"; + CARGO_PROFILE_RELEASE_OPT_LEVEL = "3"; + CARGO_PROFILE_RELEASE_PANIC = "abort"; + CARGO_PROFILE_RELEASE_INCREMENTAL = "false"; + CARGO_PROFILE_RELEASE_DEBUG = "0"; + + # Strip debug symbols + stripAllList = [ "bin" ]; + + # Use all available cores + NIX_BUILD_CORES = 0; + preBuild = '' + export CARGO_BUILD_JOBS=$NIX_BUILD_CORES + ''; + }; + + entrypoint-script = ./entrypoint.dev.sh; + +in +pkgs.dockerTools.buildLayeredImage { + name = "hxckr-core"; + tag = "latest"; + created = "now"; + + contents = [ + hxckr-core + pkgs.diesel-cli + pkgs.bash + pkgs.coreutils + pkgs.findutils + pkgs.openssl + pkgs.postgresql.lib + pkgs.cacert + pkgs.libiconv + ]; + + extraCommands = '' + mkdir -p app/migrations + cp -r ${./migrations}/* app/migrations/ + cp ${entrypoint-script} app/entrypoint.sh + chmod +x app/entrypoint.sh + ''; + + config = { + Cmd = [ "/app/entrypoint.sh" ]; + Env = [ + "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" + "PATH=/bin:${hxckr-core}/bin:${pkgs.diesel-cli}/bin:${pkgs.findutils}/bin" + "LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath [ + pkgs.openssl + pkgs.postgresql.lib + pkgs.libiconv + ]}" + ]; + WorkingDir = "/app"; + ExposedPorts = { + "4925/tcp" = {}; + }; + }; +}