Including folders in Docker Images

I’m trying to replace our Dockerfile for our rust project with a Nix generated docker image. To do that, I’ve setup crane, and am fairly close to our desired output. But I am struggling to get the folders included that need to be included at the correct paths. Here is the flake

{
  description = "Build a cargo project with a custom toolchain";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
    advisory-db = {
      url = "github:rustsec/advisory-db";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, advisory-db, rust-overlay, ... }:
    flake-utils.lib.eachDefaultSystem
      (system:
        let
          pkgs = import nixpkgs {
            inherit system;
            overlays = [ (import rust-overlay) ];
          };

          rustTarget = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
            extensions = [ "rust-src" "rust-analyzer" ];
            targets = [ "wasm32-unknown-unknown" ];
          });

          # NB: we don't need to overlay our custom toolchain for the *entire*
          # pkgs (which would require rebuidling anything else which uses rust).
          # Instead, we just want to update the scope that crane will use by appendings
          inherit (pkgs) lib;
          # our specific toolchain there.
          craneLib = (crane.mkLib pkgs).overrideToolchain rustTarget;
          #craneLib = crane.lib.${system};
          # Only keeps markdown files
          protoFilter = path: _type: builtins.match ".*proto$" path != null;
          sqlxFilter = path: _type: builtins.match ".*json$" path != null;
          tomlFilter = path: _type: builtins.match ".*toml$" path != null;
          protoOrCargo = path: type:
            (protoFilter path type) || (craneLib.filterCargoSources path type) || (sqlxFilter path type) || (tomlFilter path type);
          # other attributes omitted
          src = lib.cleanSourceWith {
            src = ./.; # The original, unfiltered source
            filter = protoOrCargo;
          };
          #    src = craneLib.cleanCargoSource ./.;

          buildInputs = [
            # Add additional build inputs here
            pkgs.pkg-config
            pkgs.openssl
            pkgs.protobuf
          ] ++ lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];


          # Build *just* the cargo dependencies, so we can reuse
          # all of that work (e.g. via cachix) when running in CI
          cargoArtifacts = craneLib.buildDepsOnly {
            inherit src buildInputs;
          };

          # Build the actual crate itself, reusing the dependency
          # artifacts from above.
          vidette = craneLib.buildPackage {
            inherit cargoArtifacts src buildInputs;
            pname = "vidette";
            # Prevent cargo test and nextest from duplicating tests
            doCheck = false;
            # ALL CAPITAL derivations will get forwarded to mkDerivation and will set the env var during build
            SQLX_OFFLINE = "true";
          };
        in
        {
          checks = {
            # Build the crate as part of `nix flake check` for convenience
            inherit vidette;

            # Run clippy (and deny all warnings) on the crate source,
            # again, resuing the dependency artifacts from above.
            #
            # Note that this is done as a separate derivation so that
            # we can block the CI if there are issues here, but not
            # prevent downstream consumers from building our crate by itself.
            vidette-clippy = craneLib.cargoClippy {
              inherit cargoArtifacts src buildInputs;
              cargoClippyExtraArgs = "--all-targets -- --deny warnings";
            };

            vidette-doc = craneLib.cargoDoc {
              inherit cargoArtifacts src buildInputs;
            };

            # Check formatting
            vidette-fmt = craneLib.cargoFmt {
              inherit src;
            };

            # Audit dependencies
            vidette-audit = craneLib.cargoAudit {
              inherit src advisory-db;
            };

            # Run tests with cargo-nextest
            # Consider setting `doCheck = false` on `vidette` if you do not want
            # the tests to run twice
            # vidette-nextest = craneLib.cargoNextest {
            #  inherit cargoArtifacts src buildInputs;
            #  partitions = 1;
            #  partitionType = "count";
            #};
          } // lib.optionalAttrs (system == "x86_64-linux") {
            # NB: cargo-tarpaulin only supports x86_64 systems
            # Check code coverage (note: this will not upload coverage anywhere)
            #vidette-coverage = craneLib.cargoTarpaulin {
            #  inherit cargoArtifacts src;
            #};

          };

          packages.default = vidette;

          # Create an option to build a docker image from this package 
          packages.container = pkgs.dockerTools.buildImage {
            name = "vidette";
            tag = "latest";
            created = "now";
            # contents = [ pkgs.bash ];
            copyToRoot = pkgs.buildEnv {
              name = "image-root";
              paths = [pkg.cacert];
              pathsToLink = ["/bin" "./configuration" "./keys" "./migrations"];
            };
            config = {
              Env = [ "PATH=${vidette}/bin" ];

              ExposedPorts = {
                "8080/tcp" = { };
              };

              Cmd = [ "${vidette}/bin/vidette" ];
            };

          };

          apps.default = flake-utils.lib.mkApp {
            drv = vidette;
          };

          devShells.default = pkgs.mkShell {
            inputsFrom = builtins.attrValues self.checks;

            # Extra inputs can be added here
            nativeBuildInputs = with pkgs; [
              rustTarget
              openssl
              dive
              sqlx-cli
              pkg-config
              protobuf
              flyctl
            ];
          };
        });
}

Running this image successfully launches my Rust app, but the directory structure is wrong. This is the structure

├── Cargo.lock
├── Cargo.toml
├── configuration
├── email
├── flake.lock
├── flake.nix
├── fly.toml
├── keys
├── migrations
├── README.md
├── sqlx-data.json
├── package_2
├── vidette
└──another_thing

I need to copy the folders keys, migrations, and configurations with their files inside so that when the vidette app is run, they are in the same dir as the created executable.
I read these threads and the linked ones, but I’m still puzzled:

I think it’s something to do with copyToRoot

EDIT: Eventually figured it out.

 copyToRoot = pkgs.buildEnv {
              name = "image-root";
              paths = [ pkgs.cacert ./.  ];
              pathsToLink = [ "/bin" "/configuration" "/keys" "/migrations" ];
            };

paths are the folders/packages to include, and pathsToLink are the paths that get Linked to the root.

1 Like