This comprehensive guide covers everything you need to know about developing with Flashboxes, from creating new modules to testing for reproducibility.
- Project Structure
- Creating a New Module
- Adding Files to Modules
- Common mkosi Configuration Options
- Custom Kernel Configuration
- Adding Source Repositories
- Creating systemd Services
- Extending Built-in systemd Services
- Freezing to Debian Archive Snapshots
- Testing for Reproducibility
- Creating Debian Packages
- Custom Developer Files
- Debugging and Troubleshooting
flashboxes/
├── shared/ # Core minimal Linux system
│ ├── mkosi.conf # Base mkosi configuration
│ ├── mkosi.skeleton/ # Base filesystem overlay
│ ├── kernel/ # Kernel configuration
│ │ ├── config.d/ # Base + config fragments
│ │ └── patches/ # Kernel patches
│ └── debloat*.sh # System cleanup scripts
├── modules/
│ ├── flashbox/
│ │ ├── common/ # TEE Searcher common image
│ │ ├── flashbox-l1/ # L1 TEE Searcher sandbox image
│ │ └── flashbox-l2/ # L2 TEE Searcher sandbox image
│ └── tdx-dummy/ # TDX test environment
├── images/ # Top-level image configs
│ ├── flashbox-l1.conf
│ ├── flashbox-l2.conf
│ └── tdx-dummy.conf
├── buildernet/ # BuilderNet
├── scripts/ # Build helper scripts
│ └── verification/ # Image verification tools
├── services/ # Shared systemd services
└── mkosi.profiles/ # Build profiles (devtools, azure)
A module in Flashboxes is a collection of configuration files that define how to build a specific type of image. Each module inherits from the base configuration and adds its own customizations.
mkdir mymodule/
cd mymodule/
# Create the basic files
touch mymodule.conf # Main configuration
touch mkosi.build # Build script (optional)
touch mkosi.postinst # Post-installation script (optional)
mkdir mkosi.extra/ # File overlays
# Scripts need the executable bit set
chmod +x mkosi.build mkosi.postinstmymodule/mkosi.conf:
[Build]
# Environment variables available in scripts
Environment=MY_CUSTOM_VAR
# Enable network access during build (needed for downloads)
WithNetwork=true
[Content]
# File overlays
ExtraTrees=mymodule/mkosi.extra
# Scripts to run during build phase
BuildScripts=mymodule/mkosi.build
# Scripts to run after package installation
PostInstallationScripts=mymodule/mkosi.postinst
# Packages to install
Packages=curl
wget
python3
# Packages needed only during build
BuildPackages=build-essential
git
golangimages/mymodule.conf (in images/):
[Include]
Include=shared/mkosi.conf
Include=mymodule/mkosi.confnix develop -c mkosi --force -I images/mymodule.confThere are two main ways to add custom files to your module. mkosi.extra is preferred because files are placed after package installation and can override default package files. To add overlay files before packages are installed, use SkeletonTrees and mkosi.skeleton instead of ExtraTrees and mkosi.extra
To add files:
mkdir -p mymodule/mkosi.extra/etc/systemd/system/
mkdir -p mymodule/mkosi.extra/usr/bin/
mkdir -p mymodule/mkosi.extra/home/myuser/
# Add a custom systemd service
cat > mymodule/mkosi.extra/etc/systemd/system/myservice.service << 'EOF'
[Unit]
Description=My Custom Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/myapp
Restart=always
[Install]
WantedBy=minimal.target
EOF
# Add a custom script
cp myscript mymodule/mkosi.extra/usr/bin/
chmod +x mymodule/mkosi.extra/usr/bin/myscript
# Add configuration files
echo "config_value=123" > mymodule/mkosi.extra/etc/myapp.confFiles copied via mkosi.extra inherit permissions from the source. To set specific permissions or ownership, use the post-installation script:
mymodule/mkosi.postinst:
#!/bin/bash
set -euxo pipefail
# Set file permissions
chmod 600 "$BUILDROOT/etc/myapp.conf"
chmod +x "$BUILDROOT/usr/bin/myapp"
# Set ownership (must use mkosi-chroot for user/group operations)
mkosi-chroot chown root:root /home/myuser/configHere are the most commonly used mkosi configuration options for modules:
[Build]
# Environment variables for scripts
Environment=VAR1 VAR2=value
# Enable network during build
WithNetwork=true[Content]
# File overlays
ExtraTrees=module/mkosi.extra
SkeletonTrees=module/mkosi.skeleton
# Build and post-install scripts
BuildScripts=module/mkosi.build
PostInstallationScripts=module/mkosi.postinst
# Package selection
Packages=package1 package2
BuildPackages=build-only-packageFor comprehensive mkosi options, see: mkosi Documentation
Flashboxes supports custom kernel configurations through base configs and snippets.
Create a custom kernel config snippet in your module folder:
module/kernel.config:
# Enable custom features
CONFIG_MY_FEATURE=y
Enable in your module:
[Build]
Environment=KERNEL_CONFIG_SNIPPETS=module/kernel.config,module/another-kernel-snippet.configThese snippets will be applied over the base configuration in shared/kernel/kernel-yocto.config
Flashboxes provides helper scripts for building software from source repositories.
Use the build_rust_package.sh script for Rust/Cargo projects:
In your mkosi.build script:
#!/bin/bash
set -euxo pipefail
source scripts/build_rust_package.sh
# Build a Rust package
build_rust_package \
"lighthouse" \ # Package name
"v7.0.1" \ # Git tag
"https://github.com/sigp/lighthouse.git" \ # Repository URL
"$LIGHTHOUSE_BINARY" \ # Pre-built binary (optional)
"modern" \ # Cargo features (optional)
"-l z -l zstd -l snappy" # Extra RUSTFLAGS (optional)
# Package will be installed to /usr/bin/lighthouseUse the make_git_package.sh script for Go and other projects:
In your mkosi.build script:
#!/bin/bash
set -euxo pipefail
source scripts/make_git_package.sh
# Build a Go package
make_git_package \
"myapp" \ # Package name
"v1.0.0" \ # Git tag
"https://github.com/user/myapp" \ # Repository URL
'go build -o ./build/myapp cmd/main.go' \ # Build command
"build/myapp:/usr/bin/myapp" # src:dest mapping
# Multiple artifacts supported
make_git_package \
"multi-tool" \
"v2.0.0" \
"https://github.com/user/multi-tool" \
'make build' \
"bin/tool1:/usr/bin/tool1" \
"bin/tool2:/usr/bin/tool2" \
"config/default.conf:/etc/multi-tool.conf"Both scripts automatically cache built artifacts based on package name and version. Cached builds are stored in $BUILDDIR/package-version/ and significantly speed up subsequent builds.
systemd services are the primary way to run applications in Flashboxes. Here's how to create and configure them.
mymodule/mkosi.extra/etc/systemd/system/myapp.service:
[Unit]
Description=My Application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/myapp --config /etc/myapp.conf
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=minimal.targetLong-running daemon:
[Service]
Type=simple # Service doesn't fork
ExecStart=/usr/bin/myapp
Restart=always # Always restart on exit
RestartSec=5 # Wait 5s before restartOne-shot service:
[Service]
Type=oneshot # Runs once and exits
ExecStart=/usr/bin/setup-task
RemainAfterExit=yes # Needed to use this service as a dependencyUser/Group isolation:
[Service]
User=myuser
Group=mygroup
# Create user in postinst script:
# mkosi-chroot useradd -r -s /bin/false myuserSecurity hardening:
[Service]
# Sandboxing
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
# Capabilities
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# Resource limits
MemoryMax=1G
CPUQuota=50%Dependency types:
[Unit]
# Must start after these services
After=network.target postgresql.service
# Require these services (will start them)
Requires=network.target
# Want these services (prefer but don't require)
Wants=postgresql.service
# Conflicts with these services
Conflicts=apache2.serviceCommon Flashboxes dependencies:
[Unit]
# Network is available
After=network-online.target
Wants=network-online.target
# Persistent storage is mounted
After=persistent-mount.service
Requires=persistent-mount.service
# Basic system is ready
After=basic.targetTo enable a service installed with a Debian package, add the following to your mkosi.postinst script:
mkosi-chroot systemctl add-wants minimal.target myapp.serviceSometimes you need to modify existing systemd services rather than creating new ones.
Create drop-in directories to override specific settings:
mymodule/mkosi.extra/etc/systemd/system/dropbear.service.d/custom.conf:
[Unit]
# Add additional dependencies
After=wait-for-key.service
Requires=wait-for-key.service
[Service]
# Override or add environment variables
Environment=DROPBEAR_EXTRA_ARGS="-s -w -g"
# Add pre-start commands
ExecStartPre=/usr/bin/setup-keysIn mkosi.postinst script:
# Disable and mask unwanted services
mkosi-chroot systemctl disable ssh.service ssh.socket
mkosi-chroot systemctl mask ssh.service ssh.socket
# Create mask symlinks manually if needed
ln -sf /dev/null "$BUILDROOT/etc/systemd/system/unwanted.service"Place a complete service file in mkosi.extra/etc/systemd/system/ to completely override the package default:
mymodule/mkosi.extra/etc/systemd/system/nginx.service:
[Unit]
Description=Custom Nginx Configuration
After=network.target
[Service]
Type=forking
ExecStart=/usr/sbin/nginx -c /etc/nginx/custom.conf
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
[Install]
WantedBy=multi-user.targetFor production deployments, it's critical to pin your builds to specific Debian archive snapshots to ensure reproducibility and security.
- Browse available snapshots: Debian Snapshot Archive
- Choose a recent stable snapshot: Ideally, pick the most recent snapshot
- Test the snapshot: Build with the snapshot and verify all packages install correctly
In your module configuration:
[Distribution]
Mirror=https://snapshot.debian.org/archive/debian/20250526T142542Z/
[Build]
ToolsTreeMirror=https://snapshot.debian.org/archive/debian/20250526T142542Z/⚠ Update snapshots regularly to get security patches
Reproducible builds are essential for security and trust. Here's how to verify your builds are deterministic.
# Build twice
mkosi --force -I mymodule.conf
cp build/mymodule-image.efi build/first-build.efi
mkosi --force -I mymodule.conf
cp build/mymodule-image.efi build/second-build.efi
# Compare hashes
sha256sum build/first-build.efi build/second-build.efi
# Should show identical hashes# Install diffoscope
apt install diffoscope
# Compare builds in detail
diffoscope build/first-build.initrd build/second-build.initrdFor distributing software that's not available in Debian repositories, you can create custom .deb packages.
mypackage-1.0/
├── DEBIAN/
│ ├── control # Package metadata
│ ├── postinst # Post-installation script
│ ├── prerm # Pre-removal script
│ ├── postrm # Post-removal script
│ └── preinst # Pre-installation script
├── usr/
│ └── bin/
│ └── myapp # Your application
└── etc/
└── myapp/
└── config.conf # Configuration files
DEBIAN/control:
Package: mypackage
Version: 1.0.0
Section: utils
Priority: optional
Architecture: amd64
Depends: libc6 (>= 2.34), libssl3
Maintainer: Your Name <your.email@example.com>
Description: My custom application
Longer description of what this package does.
Each line should be indented with a space.
DEBIAN/postinst:
#!/bin/bash
set -e
# Create system user
useradd -r -s /bin/false myapp || true
# Set permissions
chown myapp:myapp /etc/myapp/config.conf
chmod 600 /etc/myapp/config.conf
# Enable systemd service
mkosi-chroot systemctl add-wants minimal.target myapp.service || true
exit 0DEBIAN/prerm:
#!/bin/bash
set -e
# Stop service before removal
systemctl stop myapp.service || true
systemctl disable myapp.service || true
exit 0DEBIAN/postrm:
#!/bin/bash
set -e
case "$1" in
purge)
# Remove user and data on purge
userdel myapp || true
rm -rf /var/lib/myapp
;;
remove)
# Keep data on remove
;;
esac
exit 0# Build the .deb file
dpkg-deb --build mypackage-1.0
# Verify package contents
dpkg -c mypackage-1.0.deb
# Install locally for testing
sudo dpkg -i mypackage-1.0.deb- Installation:
preinst→ files copied →postinst - Upgrade:
preinst upgrade→ files copied →postinst configure - Removal:
prerm remove→ files removed →postrm remove - Purge:
prerm remove→ files removed →postrm purge
For comprehensive .deb creation, see: Debian New Maintainers' Guide
For systems without systemd v250+ or where Nix installation isn't feasible, you can use the experimental Podman containerization support. This approach is not recommended due to slower build times and a complex setup process.
- Configure the Podman daemon to use a storage driver other than OverlayFS
- The btrfs driver is fastest, but requires that you have a btrfs filesystem
- The storage driver can be configuring by editing
/etc/containers/storage.conf
- Build the development container:
sudo podman build -t flashbots-images . - Create required directories
mkdir mkosi.packages mkosi.cache mkosi.builddir build - Run the container with proper mounts and privilages
sudo podman run \ --storage-driver btrfs \ --privileged \ --cap-add=ALL \ --security-opt label=disable \ -it \ -v $(pwd)/mkosi.packages:/home/ubuntu/mkosi/mkosi.packages \ -v $(pwd)/mkosi.cache:/home/ubuntu/mkosi/mkosi.cache \ -v $(pwd)/mkosi.builddir:/home/ubuntu/mkosi/mkosi.builddir \ -v $(pwd)/build:/home/ubuntu/mkosi/build \ flashbots-imagesReplace "btrfs" with your chosen storage driver
- Run the desired
mkosicommand inside the shell Podman environment
When building with the devtools profile, you can add your own custom files to the image without committing them to git. This is useful for adding personal SSH keys, configuration files, or debugging tools during development.
Place files in mkosi.profiles/devtools/custom/ mirroring the filesystem structure you want:
# Add your SSH authorized keys
mkdir -p mkosi.profiles/devtools/custom/root/.ssh
cp ~/.ssh/id_rsa.pub mkosi.profiles/devtools/custom/root/.ssh/authorized_keys
# Add a custom configuration file
mkdir -p mkosi.profiles/devtools/custom/etc
echo "my_setting=value" > mkosi.profiles/devtools/custom/etc/myconfig.conf
# Add a debugging script
mkdir -p mkosi.profiles/devtools/custom/usr/local/bin
cp my-debug-script.sh mkosi.profiles/devtools/custom/usr/local/bin/Files placed here will be copied into the image (like any other ExtraTrees directory) but will be ignored by git, so they won't be accidentally committed.
Interactive debugging during build:
# Add to your build script where you want to break
socat UNIX-LISTEN:$SRCDIR/debug.sock,fork EXEC:/bin/bash,pty,stderr
# Then connect from host
script -qfc "socat STDIO UNIX-CONNECT:debug.sock" /dev/nullAccess the build chroot:
# From debug session
mkosi-chroot /bin/bashPackage installation failures:
# Check package availability
mkosi-chroot apt search mypackage
mkosi-chroot apt policy mypackage
# Check repository configuration
cat $BUILDROOT/etc/apt/sources.listNetwork issues during build:
[Build]
# Ensure network is enabled
WithNetwork=truePermission issues:
# Check file ownership in build
ls -la $BUILDROOT/path/to/file⚠ Sometimes, postinst files are not able to dynamically chown files. In this case, the only option is to set permissions from an early one-shot boot service.
Boot debugging:
[Content]
# Add debug options to kernel command line
KernelCommandLine=console=ttyS0,115200n8 systemd.show_status=1 systemd.log_level=debugService debugging:
# Inside running system
journalctl -u myservice.service
systemctl status myservice.serviceDevelopment profile:
# Build with debugging tools
mkosi --force -I mymodule.conf --profile devtools