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
.snapfile (the snap package you want to run) unsquashfsorsquashfs-toolsinstalled- 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
/tmpor 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:
-
Configuration Management
snapctl get <key>- Read configuration valuessnapctl set <key>=<value>- Write configuration values- Configuration is stored by snapd and persists across snap updates
-
Service Management
snapctl start/stop/restart <service>- Control snap services- Query service status
- Coordinate between multiple services in a snap
-
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
-
Health Checks
snapctl set-health- Report snap health status to snapd- Used by monitoring and management tools
-
Connection Information
- Query which interfaces are connected
- Get connection attributes and parameters
-
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_COOKIEenvironment variable (authentication token)$SNAP_CONTEXTenvironment variable (legacy)- Unix socket at
/run/snapd.socketor/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:
- Choose snaps that don't use snapctl (rare, mostly simple CLI tools)
- Patch the snap to remove snapctl dependencies (difficult)
- 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
/snapdirectory 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_DATAfor strict confinement
Library Dependencies#
- The snap may have dependencies on system libraries
- Without proper
SNAP_LIBRARY_PATHsetup, 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:
- Educational purposes: Understanding how snaps work internally
- Debugging: Examining snap contents without installing
- Offline/air-gapped systems: Where snapd cannot be installed
- Emergency recovery: Running tools from snaps when system is broken
- Embedded systems: Very constrained environments without systemd
- Research: Analyzing snap behavior and structure
Alternative: Snap-Confine Without Snapd#
If you need some confinement without full snapd, you could theoretically:
- Compile
snap-confinestandalone - Write AppArmor profiles manually
- Create seccomp filters manually
- Set up mount namespaces manually
- Configure device cgroups manually
This is extremely complex and not recommended. You're essentially reimplementing snapd.
Recommended Alternatives#
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 confinementsnap-update-ns: Mount namespace managersnapddaemon: 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.