Cappend is a tool for appending to a container. No frills, just tars.

    Cappend is substantially faster than other container building techniques by optimizing for the subset of workflows that only add files or adjust image metadata.

    Templated Example 

    The following demonstrates a moderately complex use of Cappend, but a simple example is also provided.

    .gitlab-ci.yml 

    build:
      stage: build
      image: alpine
      variables:
        CEDARCI_SERVICE_CAPPEND: "true"
        CONTAINER_TAG_BASE: "${CI_REGISTRY_IMAGE}/${CI_COMMIT_BRANCH}"
        CONTAINER_TAG_SHA: "${CONTAINER_TAG_BASE}:${CI_COMMIT_SHA}"
        CONTAINER_TAG_LATEST: "${CONTAINER_TAG_BASE}:latest"
      script: |
        # Language specific images likely come with these (or should build a container).
        apk add docker-cli jq
    
        # `target/release/app` will be `/app` within the container owned by root:root.
        tar --numeric-owner --owner=0 --group=0 \
          -cf app.tar \
          -C target/release \
          app
    
        # Fill template variable values.
        jq -n \
          --arg CONTAINER_TAG_LATEST "${CONTAINER_TAG_LATEST}" \
          --arg CONTAINER_TAG_SHA "${CONTAINER_TAG_SHA}" \
          --from-file cappend.json > cappend.final.json
        diff -u cappend.json cappend.final.json || true
    
        # Any docker compatible auth config generation will do.
        docker login \
          -u ${CI_REGISTRY_USER} \
          -p ${CI_REGISTRY_PASSWORD} \
          ${CI_REGISTRY} 2> /dev/null
    
        # Build and push.
        /cedarci/cappend cappend.final.json
    

    cappend.json 

    It can be preferable to generate the JSON from your language of choice instead of the jq template shown below.

    {
      "target": [
        $CONTAINER_TAG_LATEST,
        $CONTAINER_TAG_SHA
      ],
      "command": [
        "/app"
      ],
      "layers": [
        "app.tar"
      ]
    }
    

    Path Transform 

    Paths can be transformed by tar to re-create ADD app.tar /path.

    Adjusting the above example the file would end up as /usr/local/bin/app.

    tar --numeric-owner --owner=0 --group=0 \
      --transform 's,^,usr/local/bin/,' \
      -C target/release \
      app
    

    To retain relative symlinks utilize [^.] to avoid rewriting them since they begin with ../.

    --transform 's,^\([^.]\),opt/\1,'
    

    Reference 

    The following is the complete set of supported options.

    {
      // Required
    
      // Standard reference format that defaults to docker.io and latest tag.
      // If multiple targets are provided they must be part of the same repository.
      "target": [
        "registry.example.com/best/app",
        "registry.example.com/best/app:tag1"
      ],
    
      // Optional
    
      // Base image from which to start.
      // When not specified it is equivalent to Dockerfile `FROM scratch`.
      "base": "alpine",
    
      // Platform filters for multi-arch base images.
      // See Go Language documentation GOARCH for allowed values.
      "architecture": "amd64",
    
      // See Go Language documentation GOOS for allowed values.
      "os": "linux",
    
      // Ordered layers to append from tar files.
      // Paths are relative to this JSON file or absolute.
      "layers": [
        "path/to/app.tar",
        "other/one.tar"
      ],
    
      // Environment variables to append (does not merge).
      "environment": {
        "SOMETHING": "SPECIAL",
        "ANOTHER": "ONE"
      },
    
      // Kubernetes/CLI style command and arguments.
      // Default command (Dockerfile `ENTRYPOINT`).
      "command": [
        "/bin/sh",
        "-c"
      ],
    
      // Default arguments (Dockerfile `CMD`).
      "arguments": [
        "sleep inf"
      ],
    
      // Default directory (Dockerfile `WORKDIR`).
      "directory": "/opt",
    
      // Config and history author.
      "author": "Cedar CI",
    
      // Image config labels.
      "labels": {
        "org.opencontainers.image.description": "Special container."
      }
    }
    

    The Go Language Documentation mentioned above.