Snap

Packaging — snapcraft.yaml in detail

Every snap is built from a single recipe file, snap/snapcraft.yaml, that the snapcraft tool reads to fetch sources, build them, and assemble the result into the .snap squashfs image described in Fundamentals. This page goes through that file field by field.

Quick reference
What it is
The single YAML recipe (snapcraft.yaml) that defines every snap's metadata, apps, hooks, and build steps.
Mental model
Part manifest, part build script — identity fields say what it is, parts: says how to build it, apps: says what runs.
Key numbers
4-step build lifecycle per part: pull → build → stage → prime.
Reach for it when
You're packaging any application as a snap, from a single binary to a multi-part build.

Identity: name, version, title, summary, description

name
The snap's globally unique identifier on the store — lowercase letters, digits, and hyphens only, can't start or end with a hyphen, max 40 characters. This is what snap install <name> uses, and it has to be registered (snapcraft register) before you can push a build to the store under it.
version
A short version string, max 32 characters. Can be hardcoded, or computed at build time from a part (via adopt-info pointing at a part that sets it with craftctl set version=...) — common in CI so the snap version tracks git describe automatically.
title
A human-friendly display name for store listings, separate from the machine-oriented name.
summary
One line, 78 characters max — shown in search results and snap info.
description
The full multi-line description shown on the snap's store page.

Grade

grade: stable or grade: devel. This is a publishing gate, not a runtime setting: a snap built with devel grade can be pushed to any channel except stable, which is exactly the point — it stops an in-progress build from accidentally landing in front of users who track the stable channel. Flip it to stable once the build is actually meant to be released there.

Confinement and base

confinement: strict | classic | devmode — the same three levels covered in Fundamentals, declared here at build time rather than chosen by the installer.

base: names the base snap (e.g. core22, core24) that provides the minimal root filesystem and core libraries every strictly-confined app in this snap runs against — see Snap types & Ubuntu Core for what a base snap actually is under the hood.

Apps: command, daemon, plugs, environment

The apps: map defines every executable the snap exposes, keyed by app name (exposed as snapname.appname, or bare snapname if the app name matches the snap name).

command
The executable or script actually run, as a path relative to the snap's own root (e.g. bin/my-tool) — the field that answers "what runs when this app is invoked."
command-chain
A list of extra executables run before command, in order — the usual place for wrapper scripts that set up environment variables or paths before handing off to the real binary.
daemon
simple | forking | oneshot | notify | dbus — declares the app as a background service managed by systemd instead of something a user runs directly. Paired with daemon-scope (system/user), restart-condition, install-mode (whether it starts automatically after install).
plugs
Which interfaces (see below) this specific app needs — narrower than declaring them snap-wide when only one app actually needs, say, camera.
environment
Key/value environment variables set for this app's process.
passthrough
An escape hatch: fields written here are copied verbatim into the built meta/snap.yaml even if snapcraft doesn't have first-class support for them yet.

Hooks

Hooks are scripts snapd runs automatically at specific lifecycle moments, placed at snap/hooks/<hookname> in the source tree (ending up at meta/hooks/<hookname> in the built snap). They run just as confined as the snap's own apps.

HookFires when
installRight after the snap is first installed, before it starts running.
configureAfter install, after every refresh, and whenever snap set <name> key=value changes a configuration option.
pre-refresh / post-refreshImmediately before / after an update is applied.
removeRight before the snap is removed — the place to clean up external state the snap created.
connect-plug-<iface> / disconnect-plug-<iface>When a plug this snap declares is connected to / disconnected from a slot — e.g. restart a service so it picks up newly-granted access.
connect-slot-<iface> / disconnect-slot-<iface>The same, from the slot-providing side.

Parts: how the snap actually gets built

parts: is the actual build recipe — one or more named components, each fetched and built independently, then combined into the final snap. Snapcraft runs each part through four lifecycle steps: pull (fetch source) → build (compile) → stage (collect build output) → prime (final trim before packing into the snap).

plugin
How to build this part — nil (nothing, just copy), dump (copy a pre-built tree as-is), or language/build-system plugins like cmake, autotools, python, go, meson.
source
Where the code comes from — a local path, a git URL (with optional source-branch/source-tag), or a tarball URL.
build-packages
Host packages needed only to build this part (compilers, headers) — not shipped in the final snap.
stage-packages
Packages whose contents must ship inside the snap (runtime shared libraries the app links against, for instance).
override-pull / override-build / override-stage / override-prime
Custom shell commands replacing (or, with craftctl default, supplementing) the plugin's default behavior at each lifecycle step.
organize / stage / prime
Filters controlling which files move where, and which ones actually make it into the final snap — used to trim build artifacts that shouldn't ship.

Plugs and slots

Interfaces are how a strictly-confined snap reaches anything outside its own sandbox. A plug is a snap declaring "I need this kind of access" (network, home, camera, gpio, i2c); a slot is what provides it — usually the system itself, but sometimes another snap. snap connect <snap>:<plug> <provider>:<slot> wires them together (some interfaces auto-connect, others need an explicit connection or store review).

One interface worth knowing by name: content — lets one snap expose a directory of files (a shared library, a data set, a trained ML model) that another snap reads from its own confined filesystem view, without either snap needing broad filesystem access. This is the standard way to let a model update independently of the engine that consumes it — see the inference-snap pattern in Snap types & Ubuntu Core.

Other fields worth knowing

architectures
Which CPU architectures to build for, as build-on/run-on pairs — matters immediately once you're targeting arm64 or armhf boards rather than just amd64.
layout
Remaps paths at runtime for software that hardcodes absolute paths (e.g. /etc/foo) outside the snap's own directory, without needing to patch the upstream source.
epoch
Marks breaking changes in on-disk data format across versions, so snapd won't auto-refresh a device straight across an incompatible boundary.
license
An SPDX license expression for the store listing.
icon, contact, website, source-code, issues, donation
Store-listing metadata — cosmetic, but expected by the store's review process for published snaps.

A full annotated example

name: sensor-bridge
title: Sensor Bridge
version: '1.4.2'
summary: Reads I2C sensors and republishes over MQTT
description: |
  A small daemon that polls I2C sensors on the board and republishes
  readings over MQTT for the fleet dashboard to consume.
grade: stable
confinement: strict
base: core22

apps:
  sensor-bridge:
    command: bin/sensor-bridge
    daemon: simple
    restart-condition: on-failure
    plugs: [i2c, network]
    environment:
      LOG_LEVEL: info

parts:
  sensor-bridge:
    plugin: go
    source: .
    build-packages: [gcc]
    stage-packages: [libc6]

plugs:
  i2c:
    interface: i2c
    path: /dev/i2c-1

This ties directly back to I2C and Linux drivers — the i2c plug here is exactly what grants access to /dev/i2c-1 that a strictly-confined snap wouldn't have by default.

Real-world applications

Gotchas

Field notes