Snap layouts

Layouts modify the execution environment of a strictly-confined snap.

With layouts, you can make elements in $SNAP, $SNAP_DATA, $SNAP_COMMON accessible from locations such as /usr, /var and /etc. This helps when using pre-compiled binaries and libraries that expect to find files and directories outside of locations referenced by $SNAP or $SNAP_DATA.

Layouts can also be used to help hooks access any executables they may require.

:information_source: Layouts can only help within a snap’s environment. They cannot be used to expose elements within a snap to the host environment.

Using layouts

Layouts are transparently supported by Snapcraft when using a base snap (e.g. base: core22).

As a simple example, consider an application you want to snap that:

  • stores all data in /var/lib/foo
  • has a configuration file in /etc/foo.conf
  • uses read-only data in /usr/share/foo

A layout that allows such software to be used without snap-specific modifications can be defined as follows:

layout:
  /var/lib/foo:
    bind: $SNAP_DATA/var/lib/foo
  /usr/share/foo:
    bind: $SNAP/usr/share/foo
  /etc/foo.conf:
    bind-file: $SNAP_DATA/etc/foo.conf

None of the above filesystem modifications are visible to any other snaps, or from the wider user session. They’re only visible within the per-snap mount namespace.

Layout reference

The syntax for defining a layout is:

layout:
  <target-path>: <declaration>
  <target-path>: <declaration>
  ..

Layouts are defined as a key-value map, mapping from a <target-path> to a layout declaration. Each declaration may be one of the following:

  • symlink: <source-path>: create a symbolic link. This method is preferred because it is the cheapest; the other methods significantly increase the startup time of your application.
  • bind: <source-path>: bind-mount a directory.
  • bind-file: <source-path>: bind-mount a file
  • type: tmpfs: mount a private temporary in-memory filesystem

Layouts using bind* and tmpfs significantly increase the startup time of your snap. We recommend using symlink instead, because it has the least amount of overhead.

  /usr/lib/x86_64-linux-gnu/libEGL.so:
    symlink: $SNAP/usr/lib/x86_64-linux-gnu/libEGL.so
  /usr/lib/x86_64-linux-gnu/libGL.so:
    symlink: $SNAP/usr/lib/x86_64-linux-gnu/libGL.so

Some applications, however, might treat symlinks differently than regular files or directories so you may need to use a bind mount in those cases.

<source-path> must refer to either $SNAP, $SNAP_DATA or $SNAP_COMMON.

<target-path> can include nearly any path except for:

  • /boot
  • /dev
  • /home
  • /lib/firmware, /usr/lib/firmware
  • /lib/modules, /usr/lib/modules
  • /lost+found
  • /media
  • /proc
  • /run, /var/run
  • /sys
  • /tmp
  • /var/lib/snapd
  • /var/snap

As /lib and /run are symbolic links to /usr/lib and /var/run respectively, they require separate exceptions to ensure certain locations, such as /lib/firmware, can’t be worked around. See below for further limitations.

If <source-path> and <target-path> don’t already exist, they will be automatically created by snapd. This includes the creation of new empty files, but doesn’t include the creation of symbolic link targets. This is because snapd doesn’t know what kind of objects they may eventually point to. In the previous example, $SNAP_DATA/etc/foo.conf is created before any snap application code is executed.

Creating new files and directories in read-only spaces

Layouts can create new directories and files even in read-only locations such as /usr/share. The following declaration will create /usr/share/foo, for example, visible only to executing snap applications (it’s assumed that /usr/share/foo does not exist in the base snap declared by the application developer):

layout:
  /usr/share/foo:
    bind: $SNAP/usr/share/foo

To accomplish the above, snapd uses a temporary filesystem (tmpfs) mounted on /usr/share and populated with a set of empty files and directories. These are then used for bind mounts as well as symlinks to reconstruct the original /usr/share. This allows snapd to make /usr/share writable, and consequently, allows snapd to create /usr/share/foo and configure it as desired.

Current limitations

The following apply as of snapd 2.36:

Layouts do not work with classic snaps

This functionality only works with strictly-confined snaps, and does not work with snaps using classic confinement. This may change in the future.

New entries in / (root)

Layouts cannot currently create new top-level files or directories. For example, the following layout declaration will not work:

layout:
  /foo: # Unsupported, cannot create new top-level directories.
     bind: $SNAP/foo

Incompatible existing file, directory or symbolic link

Layouts cannot replace an existing but incompatible filesystem object. This means, for example, that files cannot replace directories or symbolic links, files cannot replace a directory, and existing symbolic links cannot be redirected to a new target. You can, however, replace a directory with another directory.

8 Likes

Works perfectly, thanks again! Using it to map tmp/log directories within my snapped web app (i. e. read-only, but the framework expects them at the app root dir) to writable bind-mounts at $SNAP_DATA. Just have to make sure that those directories do not exist in source.

3 Likes

@zyga-snapd I think I have identified an edge case issue with layouts. Mounting directories and files works fine, but if I declare two target paths for bind-file where the second path’s last component (i. e. filename) contains all of the first path’s plus a suffix, snap installation will fail with:

error: cannot perform the following tasks:
- Setup snap "my-snap" (unset) security profiles (cannot setup mount for snap "my-snap": cannot update mount namespace of snap "my-snap": cannot update preserved namespace of snap "my-snap": cannot update snap namespace: no such file or directory)
- Setup snap "my-snap" (unset) security profiles (cannot update mount namespace of snap "my-snap": cannot update preserved namespace of snap "my-snap": cannot update snap namespace: no such file or directory)

My layout definition:

$SNAP/api/Gemfile:
    bind-file: $SNAP_DATA/Gemfile
$SNAP/api/Gemfile.lock:
    bind-file: $SNAP_DATA/Gemfile.lock

As you can see, the second target path just adds .lock to the filename. If I rename the first mount “Gemfile” to “Gemfile_B”, everything works fine – but this breaks my Rails application. I can configure an alternative name for Gemfile, but Rails expects a $filename.lock file for whatever name I choose. To rule out that layouts has a problem with suffix-less files, I tried a layout with Gemfile.gems and Gemfile.gems.lock to no avail.

I also tested it with bind folders (attempting to mount $SNAP/folder alongside $SNAP/folder.suffix) just to make sure it’s not specific to bind-file.

Am I “holding it wrong” ™ or is this actually an issue with the layouts feature?

I wrote a small test case based on your report and I’m running it now against master. The test passed. I will now revert a bug fix I landed to that area last week to ensure that it was the culprit.

EDIT: Some more debugging I cannot reproduce the problem just yet. Can you share more details about your setup:

  1. Were the files $SNAP/api/Gemfile and $SNAP/api/Gemfile.lock present in the snap?
  2. Did this test on core or classic system? (snap version output is best)
  3. Were you experimenting with this snap and it was already installed (and perhaps invoked at least once) or was this a clean install?
2 Likes

Thanks for the quick feedback, here are the requested details:

  1. $SNAP/api/Gemfile and $SNAP/api/Gemfile.lock were present during snap creation, but the override-build script of the Rails part renames them to Gemfile_installed Gemfile.lock_installed. I verified that they are not present in the final snap.
  2. Both:

On classic Ubuntu Desktop (snap built with LXD container)

snap   2.35.2
snapd  2.35.2
series 16
ubuntu 18.04
kernel 4.150-36-generic

on Ubuntu Core device:

snap    2.35.2
snapd   2.35.2
series  16
kernel  4.4.0-1098-raspi2
  1. Clean install – or rather attempting to install, since it aborts the installation because of said error. I made sure that no previous versions were installed during the installation attempt.

I’m looking at reproducing this with the new details right now.

EDIT: I failed reproducing this. Here is what I tried. I used snapd 2.35.4 from Ubuntu Cosmic:

snap    2.35.4+18.10
snapd   2.35.4+18.10
series  16
ubuntu  18.10
kernel  4.18.0-8-generic

There I created an application with the following definition:

name: app
version: 1
apps:
    app:
        command: bin/app
layout:
    $SNAP/api/tmp:
        bind: $SNAP_DATA/rails/tmp
    $SNAP/api/log:
        bind: $SNAP_DATA/rails/log
    $SNAP/api/vendor:
        bind: $SNAP_DATA/rails/vendor
    $SNAP/api/Gemfile:
        bind-file: $SNAP_DATA/rails/Gemfile
    $SNAP/api/Gemfile.lock:
        bind-file: $SNAP_DATA/rails/Gemfile.lock

I made a dummy bin/app shell script that just prints ok. Apart from bin/app and meta/snap.yaml there were no other files present in the snap.

Running the application I get ok and a working layout. I can then run snap run --shell app to explore the environment.

zyga@fyke:$ snap run --shell app
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

zyga@fyke:$ cd $SNAP
zyga@fyke:/snap/app/x1$ find
.
./api
./api/vendor
./api/tmp
./api/log
./api/Gemfile
./api/Gemfile.lock
./meta
./meta/snap.yaml
./bin
./bin/app

Looking at $SNAP_DATA, I see:

zyga@fyke:/snap/app/x1$ cd $SNAP_DATA
zyga@fyke:/var/snap/app/x1$ find
.
./rails
./rails/Gemfile.lock
./rails/tmp
./rails/Gemfile
./rails/vendor
./rails/log

Lastly I can inspect the mount namespace but this requires more privileges so I leave the confined shell (exit) and inspect the mount namespace using the nsenter command.

zyga@fyke:/var/snap/app/x1$ exit
exit

zyga@fyke:$ sudo nsenter -m/run/snapd/ns/app.mnt
root@fyke:/# cat /proc/self/mountinfo | tail -n 6
2130 2257 0:56 / /snap/app/x1/api rw,relatime - tmpfs tmpfs rw,mode=775,uid=1000,gid=1000
2067 2130 8:1 /var/snap/app/x1/rails/Gemfile.lock /snap/app/x1/api/Gemfile.lock rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro
2131 2130 8:1 /var/snap/app/x1/rails/Gemfile /snap/app/x1/api/Gemfile rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro
2132 2130 8:1 /var/snap/app/x1/rails/log /snap/app/x1/api/log rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro
2133 2130 8:1 /var/snap/app/x1/rails/tmp /snap/app/x1/api/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro
2134 2130 8:1 /var/snap/app/x1/rails/vendor /snap/app/x1/api/vendor rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro

As you can see /snap/app/x1/api is a tmpfs populated with the objects from $SNAP_DATA (in this case /var/snap/app/x1/).

Can you tell me what I am missing in this test?

1 Like

(Very belated answer, but fortunately I left this browser tab open :smile: )

Thank you for this extensive test! The only difference is that in my snap, $SNAP/api is an existing directory created during snap creation – and that my snap has an install hook attempting to do stuff in the directories that I specified in layouts.

Short version: The bug sat in front of my computer :smile: I misread the docs about nested paths in bind-mounts and added mkdir commands to my install hook at some point. I previously used the $SNAP_DATA/rails_{$folder} version, but then switched to $SNAP_DATA/rails/$folder – and added an mkdir $SNAP_DATA/rails/$folder, which then tried to overwrite the bind-mount.

Takeaway: Maybe the error message could be clearer, but not a fault of layouts.

Woot, that’s great to know. The up side is that 2.36 ships with layouts enabled and that nested directory limitation lifted now. Please try edge again and see if you can simplify your snapcraft.yaml now. I also heard that snapcraft will support layouts directly (CC @kyrofa to confirm please) so that the passthrough section can go away.

Yessir, but only in edge right now, it’ll be in the next stable release (but only if using bases).

1 Like

@zyga-snapd, @degville this should probably be mentioned and linked to from The snap format

I’ve just added this to The snap format document. Thanks!

1 Like

Just wanted to give feedback that layouts have been working fine for me since 2.36 and switching to snapcraft v3 was seamless in that regard. I have had the passthrough wrapper active because I’m using launchpad for ARM builds and when I last checked, those builders didn’t have layouts enabled by default.

One minor hiccup that might be related to layouts: My snap’s install hook performs some tasks in the writable mounted directories. If the hook fails (because I had a thinko or something was missing), any attempt to reinstall a newer, fixed revision of the same snap will throw an error during the install hook that the layout mount at /var/snap/my-snap/current/some/subdir does not exist. Once I reboot, the error goes away and I can install the fixed revision just fine.

@tobias, is your issue similar to the one I encountered here: Snapd 2.37.1 refresh broke layouts snap remove/install ?

It would be interesting to see if you have a similar issue with the mount point showing up with //deleted at the end. You could try logging to $HOME the output of cat /proc/self/mountinfo during the hook.

@ijohnson The snap does not get installed when install fails, so I can’t really look inside the snap. I’ll see if I can throw together a test case that’s triggered on-demand after installation.

Interesting, please file a bug. It seems that on failed install we don’t discard any namespaces we have created.

Done: https://bugs.launchpad.net/snapcraft/+bug/1815722

Nice edits, really appreciated!

I wonder if this feature is compatible with the SNAPCRAFT_ARCH_TRIPLET variable?

Too lazy to verify it myself :expressionless:

I wonder what’s the usecase of symlink ?

Not everything has to be a bind mount. A symlink is much more lightweight way to relocate something.

2 Likes