I have sinned, dear Father // Father, I have sinned

Running Snaps Without Systemd: A Manual Approach#

Overview#

This guide documents how to manually run a simple snap (command-line tool, no services) when systemd is not available. This is an educational exercise to understand snap internals—this approach is NOT recommended for production use.

Prerequisites#

  • A .snap file (the snap package you want to run)
  • unsquashfs or squashfs-tools installed
  • Basic understanding of Linux filesystems and environment variables
  • Root or appropriate permissions for mounting

Understanding Snap Structure#

A .snap file is a SquashFS filesystem containing:

  • /meta/snap.yaml - Snap metadata
  • Application binaries and data
  • Libraries and dependencies
  • Desktop files, icons, etc.

The snap expects to be mounted at a specific location (default: /snap/<snap-name>/<revision>).

Manual Steps to Run a Snap#

Step 1: Extract or Mount the Snap#

Option A: Extract the Snap (Simpler, No Root Required)#

# Extract the snap to a directory
mkdir -p ~/snaps/extracted
unsquashfs -d ~/snaps/extracted/my-snap /path/to/my-snap_1.0_amd64.snap

# Note the extraction path
SNAP_MOUNT=/home/yourusername/snaps/extracted/my-snap

Option B: Mount the Snap (More Authentic)#

# Create a mount point
sudo mkdir -p /snap/my-snap/x1

# Mount the squashfs file
sudo mount -t squashfs -o ro,nodev /path/to/my-snap_1.0_amd64.snap /snap/my-snap/x1

# Set mount point
SNAP_MOUNT=/snap/my-snap/x1

Step 2: Parse the Snap Metadata#

# Read the snap.yaml to find the app command
cat $SNAP_MOUNT/meta/snap.yaml

# Example output:
# name: hello
# version: 2.10
# apps:
#   hello:
#     command: bin/hello

Step 3: Set Up Required Environment Variables#

Snaps expect specific environment variables to be set. Create a script or export these:

#!/bin/bash

# Basic snap environment variables
export SNAP="$SNAP_MOUNT"
export SNAP_NAME="my-snap"
export SNAP_VERSION="1.0"
export SNAP_REVISION="x1"
export SNAP_ARCH="amd64"  # or your architecture

# Data directories (writable areas for the snap)
export SNAP_DATA="/var/snap/$SNAP_NAME/$SNAP_REVISION"
export SNAP_COMMON="/var/snap/$SNAP_NAME/common"

# User-specific data directories
export SNAP_USER_DATA="$HOME/snap/$SNAP_NAME/$SNAP_REVISION"
export SNAP_USER_COMMON="$HOME/snap/$SNAP_NAME/common"

# Instance-related (for regular snaps, same as SNAP_NAME)
export SNAP_INSTANCE_NAME="$SNAP_NAME"
export SNAP_INSTANCE_KEY=""

# Library path for graphics drivers, etc.
export SNAP_LIBRARY_PATH="/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32"

# User ID information
export SNAP_UID=$(id -u)
export SNAP_EUID=$(id -u)

# Exposed home directory (uppercase version)
export SNAP_REAL_HOME="$HOME"

# For classic confinement, preserve TMPDIR
# For strict confinement, you might want a snap-specific tmp
# export TMPDIR="/tmp/snap.$SNAP_NAME"

Step 4: Create Data Directories#

# Create system-wide data directories (may require sudo)
sudo mkdir -p /var/snap/$SNAP_NAME/$SNAP_REVISION
sudo mkdir -p /var/snap/$SNAP_NAME/common

# Create user data directories
mkdir -p $HOME/snap/$SNAP_NAME/$SNAP_REVISION
mkdir -p $HOME/snap/$SNAP_NAME/common

Step 5: Run the Application#

# Source the environment (or use the script above)
source /path/to/snap-env.sh

# Execute the command from snap.yaml
# If the command is "bin/hello", then:
$SNAP/bin/hello [arguments]

# Or more generally:
cd $SNAP
./path/to/command

Complete Example Script#

Here's a complete script to run a simple snap:

#!/bin/bash
set -e

# Configuration
SNAP_FILE="/path/to/my-snap_1.0_amd64.snap"
SNAP_NAME="my-snap"
SNAP_REVISION="x1"
EXTRACT_DIR="$HOME/snaps/extracted"
MOUNT_DIR="$EXTRACT_DIR/$SNAP_NAME"

# Step 1: Extract the snap
echo "Extracting snap..."
mkdir -p "$EXTRACT_DIR"
unsquashfs -f -d "$MOUNT_DIR" "$SNAP_FILE"

# Step 2: Parse snap.yaml to find the command
echo "Reading snap metadata..."
COMMAND=$(grep -A 2 "^  $SNAP_NAME:" "$MOUNT_DIR/meta/snap.yaml" | grep "command:" | awk '{print $2}')
VERSION=$(grep "^version:" "$MOUNT_DIR/meta/snap.yaml" | awk '{print $2}')

echo "Found command: $COMMAND"
echo "Version: $VERSION"

# Step 3: Set up environment
export SNAP="$MOUNT_DIR"
export SNAP_NAME="$SNAP_NAME"
export SNAP_VERSION="$VERSION"
export SNAP_REVISION="$SNAP_REVISION"
export SNAP_ARCH=$(uname -m)

export SNAP_DATA="/var/snap/$SNAP_NAME/$SNAP_REVISION"
export SNAP_COMMON="/var/snap/$SNAP_NAME/common"
export SNAP_USER_DATA="$HOME/snap/$SNAP_NAME/$SNAP_REVISION"
export SNAP_USER_COMMON="$HOME/snap/$SNAP_NAME/common"

export SNAP_INSTANCE_NAME="$SNAP_NAME"
export SNAP_INSTANCE_KEY=""
export SNAP_LIBRARY_PATH="/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32"
export SNAP_UID=$(id -u)
export SNAP_EUID=$(id -u)
export SNAP_REAL_HOME="$HOME"

# Step 4: Create data directories
echo "Creating data directories..."
mkdir -p "$SNAP_USER_DATA"
mkdir -p "$SNAP_USER_COMMON"

# For system directories, you might need sudo:
# sudo mkdir -p "$SNAP_DATA"
# sudo mkdir -p "$SNAP_COMMON"

# Step 5: Run the application
echo "Running snap application..."
cd "$SNAP"
exec ./$COMMAND "$@"

What Will Be Missing#

Running snaps manually without the snapd ecosystem means you lose significant functionality:

1. No Confinement/Security#

  • AppArmor profiles: Not loaded, snap runs without MAC (Mandatory Access Control)
  • Seccomp filters: Not applied, snap has access to all syscalls
  • Device cgroup: Not configured, snap can access all devices
  • Mount namespaces: Not created, snap sees the entire host filesystem
  • User namespaces: Not used for privilege separation

Impact: The snap runs with no security boundaries. A malicious snap could compromise your entire system.

2. No Automatic Updates#

  • No connection to Snap Store
  • No background update checks
  • No automatic snap refreshes
  • Must manually download and replace snap files

Impact: You must manually track and install updates. Security patches won't be applied automatically.

3. No Interface Connections#

  • No access to plugs and slots system
  • Cannot connect to system resources like:
    • Network Manager
    • Audio (PulseAudio/PipeWire)
    • Bluetooth
    • Hardware devices (cameras, printers, etc.)
  • No automatic permission management

Impact: The snap may fail if it expects interface connections. Many features won't work.

4. No Services#

  • Cannot run systemd services defined in the snap
  • No daemon management (start/stop/restart)
  • No socket activation
  • No timer-based execution
  • No automatic service restarts on failure

Impact: Only command-line applications can be run. Background services, daemons, and scheduled tasks won't work.

5. No Mount Management#

  • No dynamic mount namespace setup
  • Layouts defined in snap.yaml won't be applied
  • No bind mounts for content sharing
  • No per-snap /tmp or other private mounts

Impact: Applications expecting specific filesystem layouts will fail. Content interfaces won't work.

6. No State Management#

  • No tracking of installed snaps
  • No revision management
  • No rollback capability
  • No state.json or other snapd state files

Impact: Cannot track what's installed, cannot rollback to previous versions.

7. No snapctl Integration#

What is snapctl?#

snapctl is a command-line tool that snaps use to communicate with the snapd daemon. It's the bridge between a snap application and the snap system's runtime services. When a snap executes, snapd sets up environment variables (like $SNAP_COOKIE) that allow snapctl to authenticate and communicate with the snapd daemon over a Unix socket.

Why is it needed?#

Snaps use snapctl for several critical functions:

  1. Configuration Management

    • snapctl get <key> - Read configuration values
    • snapctl set <key>=<value> - Write configuration values
    • Configuration is stored by snapd and persists across snap updates
  2. Service Management

    • snapctl start/stop/restart <service> - Control snap services
    • Query service status
    • Coordinate between multiple services in a snap
  3. Hook Execution Context

    • Hooks (install, configure, pre-refresh, post-refresh, etc.) run with snapctl access
    • Hooks use snapctl to validate configuration, set defaults, perform migrations
    • Essential for snap lifecycle management
  4. Health Checks

    • snapctl set-health - Report snap health status to snapd
    • Used by monitoring and management tools
  5. Connection Information

    • Query which interfaces are connected
    • Get connection attributes and parameters
  6. System Information

    • Query snap revision, version, architecture
    • Get information about other installed snaps

The Problem#

Without snapd running, snapctl has nowhere to connect. The command will fail with:

error: snapctl: cannot invoke snapctl operation commands from outside of a snap

Or if it tries to connect:

error: cannot communicate with server: connection refused

Many "enterprise" or complex snaps (like charmcraft, lxd, multipass, etc.) heavily rely on snapctl for configuration management. They expect to be able to read/write configuration through snapctl, which breaks completely without snapd.

Detection Mechanism#

Snaps detect they're running under snapd via:

  • $SNAP_COOKIE environment variable (authentication token)
  • $SNAP_CONTEXT environment variable (legacy)
  • Unix socket at /run/snapd.socket or /run/snapd-snap.socket

Libraries like snaphelpers (Python) automatically try to use snapctl and fail when it's unavailable.

Workarounds#

1. Mock Configuration Files

Create a fake configuration that the snap might read:

# Some snaps fall back to reading config files
mkdir -p "$SNAP_DATA"
cat > "$SNAP_DATA/config.json" << EOF
{
  "key": "value"
}
EOF

Limitation: Only works if the snap has fallback code paths. Most don't.

2. Stub snapctl Command

Create a fake snapctl that returns empty/default values:

#!/bin/bash
# Fake snapctl that returns empty config
case "$1" in
  get)
    echo "{}"  # Return empty JSON config
    ;;
  set)
    exit 0  # Pretend set succeeded
    ;;
  *)
    exit 1
    ;;
esac

Add it to PATH before running the snap:

export PATH="/path/to/fake-snapctl:$PATH"

Limitation:

  • Very brittle, depends on what the snap expects
  • Doesn't work for snaps that validate responses
  • Can cause unexpected behavior if snap makes decisions based on config

3. Patch the Snap

For Python-based snaps using snaphelpers:

# Extract the snap
unsquashfs -d my-snap my-snap.snap

# Modify the code to skip snapctl
# Example: patch snaphelpers to not use snapctl
vim my-snap/lib/python3.12/site-packages/snaphelpers/_conf.py

# Repack (requires snap-pack tool, part of snapd)
snap pack my-snap/

Limitation:

  • Requires understanding the snap's internals
  • Must repack the snap (needs snapd tools!)
  • Breaks snap signatures
  • Must repeat for every snap update

4. Run with Environment Variables

Some snaps check for specific environment variables and disable features:

# Tell snap not to use snapctl (if supported)
export SNAP_TESTING=1
export IGNORE_SNAP_CONFIG=1

# Run the snap
./my-snap

Limitation: Only works if the snap explicitly supports these flags (rare).

5. Use Classic Confinement Snaps

Classic confinement snaps sometimes have less dependency on snapctl:

# Classic snaps might work better without snapd
# But classic snaps typically need snapd to install anyway

Limitation: Classic snaps still often use snapctl.

Real-World Example: charmcraft#

Charmcraft (as seen in the error above) uses the craft-application framework which uses snaphelpers for configuration. On startup, it tries:

debug_mode = self.services.get("config").get("debug")

This internally calls:

snapctl.run("get", "-d", "debug")

Which fails immediately without snapd.

Workaround: Patch craft-application or set environment to skip config:

# This doesn't actually work for charmcraft, just illustrative
export CRAFT_DEBUG=0  # Try to bypass config system

Unfortunately, charmcraft doesn't have a clean way to disable snapctl usage.

Bottom Line#

There is no general workaround for snapctl. Each snap that uses it must be handled individually:

  • Simple snaps that only use snapctl for optional features might work
  • Complex snaps that depend on snapctl for core functionality will fail
  • The only real solution is to run snapd

If you cannot run snapd, you must:

  1. Choose snaps that don't use snapctl (rare, mostly simple CLI tools)
  2. Patch the snap to remove snapctl dependencies (difficult)
  3. Use alternative packaging formats (AppImage, static binaries, containers)

Snaps that commonly use snapctl (avoid these without snapd):

  • charmcraft, lxd, multipass, microk8s
  • Most "platform" or "enterprise" snaps
  • Snaps with complex configuration
  • Snaps with services and hooks

Snaps that might work (less snapctl usage):

  • Simple CLI tools: shellcheck, yq, jq-alike tools
  • Static binaries wrapped as snaps
  • Snaps with no configuration or hooks

8. No Command Chain Support#

  • Command chains defined in snap.yaml won't execute
  • No wrapper script execution
  • No environment setup by command chains

Impact: Some snaps rely on command chains for initialization. These will fail silently.

9. No User Data Migration#

  • No automatic migration of user data between revisions
  • No XDG directory symlink management
  • No exposed home directory setup

Impact: User data management is manual and error-prone.

10. No Desktop Integration#

  • Desktop files not registered with system
  • Application won't appear in application menus
  • Icons not installed to system locations
  • MIME type associations not created
  • URL handlers not registered

Impact: The application won't integrate with your desktop environment.

11. No Parallel Installs#

  • Cannot install multiple versions or instances
  • No instance key support
  • Name conflicts if multiple versions exist

Impact: Limited to one "installation" per snap name.

12. No Graphics Acceleration#

  • No automatic graphics driver mounting
  • NVIDIA/AMD drivers not available inside snap
  • No access to /var/lib/snapd/lib/gl

Impact: Graphics applications may fail or run with software rendering only.

13. No Snap-Confine Isolation#

  • No PID 1 mount namespace tricks
  • No /snap directory bind mounts
  • No namespace file creation in /run/snapd/ns/
  • No cgroup tracking for process management

Impact: Multiple issues with process isolation and management.

14. No Seed/Preseeding Support#

  • Cannot participate in system seeding
  • Not part of system image creation
  • Cannot be part of Ubuntu Core images

Impact: Cannot be used in embedded or appliance scenarios.

15. No Assertions/Signing Verification#

  • Snap signatures not verified
  • Snap declarations not checked
  • No trust chain validation
  • No revision authority verification

Impact: Cannot verify snap authenticity or integrity. Could run tampered snaps.

Additional Caveats#

Environment Variables#

The snap may expect additional environment variables:

  • $SNAP_COOKIE - For snapctl communication (won't work)
  • $SNAP_CONTEXT - Legacy snapctl communication (won't work)
  • $XDG_RUNTIME_DIR - Should point to snap-specific runtime dir
  • $HOME - Typically overridden to $SNAP_USER_DATA for strict confinement

Library Dependencies#

  • The snap may have dependencies on system libraries
  • Without proper SNAP_LIBRARY_PATH setup, libraries won't be found
  • Classic confinement snaps expect host libraries

Path Issues#

  • The snap may expect to run from specific mount points (/snap/...)
  • Some snaps hardcode paths assuming standard snap mount locations
  • Environment variable expansion ($SNAP, etc.) must work correctly

Permissions#

  • File permissions inside the snap may not match your user
  • SELinux/AppArmor contexts won't be correct
  • Extended attributes may cause issues

Use Cases for Manual Snap Running#

Despite all the limitations, manual snap running might be useful for:

  1. Educational purposes: Understanding how snaps work internally
  2. Debugging: Examining snap contents without installing
  3. Offline/air-gapped systems: Where snapd cannot be installed
  4. Emergency recovery: Running tools from snaps when system is broken
  5. Embedded systems: Very constrained environments without systemd
  6. Research: Analyzing snap behavior and structure

Alternative: Snap-Confine Without Snapd#

If you need some confinement without full snapd, you could theoretically:

  1. Compile snap-confine standalone
  2. Write AppArmor profiles manually
  3. Create seccomp filters manually
  4. Set up mount namespaces manually
  5. Configure device cgroups manually

This is extremely complex and not recommended. You're essentially reimplementing snapd.

If you're in a restricted environment, consider these alternatives:

1. Static Binaries#

  • Use statically compiled binaries instead
  • Tools like Go produce static binaries
  • No runtime dependencies

2. AppImage#

  • Self-contained application bundles
  • Don't require systemd
  • Mount via FUSE (which has its own requirements)

3. Flatpak#

  • Also uses namespaces but has different requirements
  • Can run without systemd (with limitations)

4. Docker/Podman Containers#

  • Better isolation without snapd
  • More mature tooling for constrained environments

5. Traditional Packages#

  • Distribution packages (deb, rpm)
  • Follows system conventions
  • Better integration without systemd

Conclusion#

Running snaps without snapd and systemd is possible for very simple, statically-linked command-line tools, but you lose:

  • All security features (AppArmor, seccomp, namespaces)
  • All management features (updates, rollback, configuration)
  • All integration features (interfaces, desktop integration, services)
  • All snapd-specific functionality

The effort required grows exponentially with snap complexity. For anything beyond basic CLI tools, the missing functionality makes this approach impractical.

Bottom line: If you can't install systemd, snaps are not the right packaging format for your use case. Consider the alternatives listed above.

References#

  • Snapd Source Code
  • SquashFS Documentation
  • Key snapd components:
    • snap-confine: Confinement helper (requires compiled binary)
    • snap-exec: Execution helper inside confinement
    • snap-update-ns: Mount namespace manager
    • snapd daemon: State management and API server

Warning#

⚠️ This guide is for educational purposes only. Running snaps without proper confinement is a security risk. The snap has full access to your system, just like any other executable. Only run snaps from trusted sources, and never use this approach in production environments.