How to chisel an existing rock

Having additional utilities inside a rock can be useful for development and testing purposes. However, when moving to production, you want to make your rock as lean and secure as possible, getting rid of all the unnecessary bits and thus reducing its attack surface, while retaining its functionality.

For this, you’ll want to ensure that your rock has a bare base and that its contents are chiselled.

For this guide, let’s take the example of a Python runtime rock.

A Python rock

Our starting point is an Ubuntu 22.04-based Python rock, described by the following rockcraft.yaml file:

name: python
base: [email protected]  # based on Ubuntu Jammy
version: "3.11"
summary: Python3.11 rock
description: Example of a Python rock
platforms:
  amd64:
run-user: _daemon_  # setting a non-root user for the rock
parts:
  install-python:
    plugin: nil
    # install Python3.11
    stage-packages:
      - python3.11

This rock can be built by running:

rockcraft pack

The resulting rock (python_3.11_amd64.rock) will have approximately 42MB and have a functional Python3.11 interpreter. You can verify that by running a very simply “Hello, world” Python script in it, with Docker:

sudo rockcraft.skopeo --insecure-policy copy oci-archive:python_3.11_amd64.rock docker-daemon:python-rock:3.11-22.04
docker run --rm python-rock:3.11-22.04 exec python3.11 -c 'print("Hello, world")'

This rock will also have other utilities like bash and apt which come from the rock’s underlying Ubuntu 22.04 base.

Rebuild the rock with a bare base

When starting to prepare the rock for production, the main goal is to get rid of all the software that is not necessary at runtime, and the first step towards achieving that goal is to use a bare base.

In a separate directory, copy the rockcraft.yaml from above and replace base: ubuntu@22.04 with base: bare. With this change, you must also remember to tell Rockcraft which Ubuntu release to use for its build environment. Do this by adding build-base: ubuntu@22.04 to the rockcraft.yaml file, which should now look like this:

name: bare-python
base: bare  # using a bare base
build-base: [email protected]  # using Ubuntu Jammy only for the build environment
version: "3.11"
summary: Python3.11 rock with a bare base
description: Example of a distroless-like Python rock
platforms:
  amd64:
run-user: _daemon_  # setting a non-root user for the rock
parts:
  install-python:
    plugin: nil
    # install Python3.11
    stage-packages:
      - base-files  # also install base-files to ensure a basic filesystem structure
      - python3.11

Pack this rock with the same rockcraft command as above:

rockcraft pack

This new rock (bare-python_3.11_amd64.rock) will now have about 28MB - a ~33% size reduction - and also have a functional Python3.11 interpreter. Run the same “Hello, world” Python script as before to confirm:

sudo rockcraft.skopeo --insecure-policy copy oci-archive:bare-python_3.11_amd64.rock docker-daemon:python-rock:3.11-bare
docker run --rm python-rock:3.11-bare exec python3.11 -c 'print("Hello, world")'

The question then is: how is rockcraft able to produce an equally functional Python rock with such a drastic reduction in size?

And the answer is: the rock no longer has the Ubuntu base as its first layer, and thus no longer has utilities like ``bash`` and ``apt`` (which aren’t needed for this use case anyway)!

Chisel the Python rock

The last step in your way to production is to chisel the rock in order to further reduce its size and attack surface, by stripping down the internal Python components that are not needed for the final application.

In this example, the Python application is a very simple “Hello, world”, which means we need nothing else but the core Python modules. For that, copy the previous rockcraft.yaml file to a new directory and simply append the desired package slice names to the list of stage-packages, like this:

name: chiselled-python
base: bare  # using a bare base
build-base: [email protected]  # using Ubuntu Jammy only for the build environment
version: "3.11"
summary: Chiselled Python3.11 rock
description: Example of a chiselled Python rock
platforms:
  amd64:
run-user: _daemon_  # setting a non-root user for the rock
parts:
  install-python-slices:
    plugin: nil
    stage-packages:
      - base-files_release-info # we only want the release information
      - python3.11_core # we just need the Python core functionality

Pack it with:

rockcraft pack

And the end result will be an astoundingly small Python rock with 13MB! And the “Hello, world” script still works:

sudo rockcraft.skopeo --insecure-policy copy oci-archive:chiselled-python_3.11_amd64.rock docker-daemon:python-rock:3.11-chiselled
docker run --rm python-rock:3.11-chiselled exec python3.11 -c 'print("Hello, world")'

To conclude, you’ve just created a general-purpose Python rock with just a few YAML lines and no code whatsoever! Then, by changing a couple of YAML fields (the base), you’ve achieved a ~33% size reduction while maintaining functionality. Finally, by appending two words (literally, just the slice names) to the rockcraft.yaml, you were able to reduce the rock’s size even further by an additional ~37% of its original size! In short:

Original rock

w/ bare base

chiselled

42MB

28MB

13MB