Skip to main content

How to: Run Apps in Syftbox

This guide explains how to run Apps in Syftbox, including common execution patterns and examples.

Understanding Syftbox Execution

Syftbox orchestrates the execution of your applications by:

  • Looking through all folders within Syftbox/apps
  • For each application that has a run.sh script, executes it in a linear fashion
  • By default, repeating this process every 10 seconds

This automated execution pattern means your applications will be re-run every 10s, similar to a cronjob, unless you implement logic to control their execution frequency.

You are probably familiar by now with how a run.sh script looks like, but here's a quick refresher:

#!/bin/sh
set -e

# Create and activate virtual environment
[ ! -d .venv ] && uv venv
. .venv/bin/activate

# Install dependencies
uv pip install -r requirements.txt

# Run the application
python3 main.py

# Cleanup
deactivate

Common Execution Patterns

Now, let's go together through a series of common patterns that might be a good fit for the Apps you want to run:

  • Pattern 1: Run only once
  • Pattern 2: Separate initialization and main execution
  • Pattern 3: Customize execution frequency
  • Pattern 4: Long-running process (service)

Pattern 1: Run Only Once

If you want your App to execute only once, you need to add logic to the run.sh to mark when it is first executed and check if it has already run in subsequent executions.

Example run.sh:

#!/bin/sh
set -e

# Skip if already run
[ -f "./already_run.flag" ] && { echo "Already executed. Skipping."; exit 0; }

# Setup environment
[ ! -d .venv ] && uv venv
. .venv/bin/activate
uv pip install -r requirements.txt

# Run application
python3 main.py

# Mark as completed
touch "./already_run.flag"
deactivate

How it works:

  • The script first checks if a file named already_run.flag exists in the current directory
  • If the flag file exists, it exits immediately
  • Otherwise, it sets up the environment and runs the application as normal
  • After successful execution, it creates the flag file and this flag file will prevent the script from running again in future

Pattern 2: Separate Initialization and Main Execution

For Apps where you want to run initialization once, but the main logic repeatedly according to the Syftbox re-execution paradigm:

Example run.sh:

#!/bin/sh
set -e

# Setup environment
[ ! -d .venv ] && uv venv
. .venv/bin/activate
uv pip install -r requirements.txt

# Run initialization once
if [ ! -f "./init_complete.flag" ]; then
python3 init.py
touch "./init_complete.flag"
fi

# Always run main process
python3 main.py

deactivate

How it works:

  • The script first sets up the environment, which is needed regardless of initialization status
  • Checks if a file named init_complete.flag exists
  • If the flag doesn't exist, it runs the initialization script (init.py) and creates the flag file to mark initialization as complete
  • Continues to always run the main application (main.py)

Pattern 3: Custom Execution Frequency

If you want to control the frequency at which your App runs, you can implement time-based checks. This can be done either in the run.sh or within your main code. If your code is in Python, this is an example of how you can implement it:

Example Python code (main.py):

import os
import json
from datetime import datetime, UTC

def should_run(file_path, interval=300):
"""Check if enough time has passed since last run"""
if not os.path.exists(file_path):
return True

try:
with open(file_path, 'r') as f:
last_run = datetime.fromisoformat(json.load(f)["last_run"])
except:
return True

seconds_since_last_run = (datetime.now(UTC) - last_run).total_seconds()
return seconds_since_last_run >= interval

def main():
from syftbox.lib import Client

# Settings
INTERVAL = 3600 # Run hourly

# Setup paths
client = Client.load()
output_folder = client.app_data("my_hourly_app")
timestamp_file = output_folder / "last_run.json"

# Check if it's time to run
if not should_run(timestamp_file, INTERVAL):
print(f"Too soon to run again. Waiting for {INTERVAL}s interval.")
return

# Your App logic here
# ...
# ...
print(f"Running App at {datetime.now(UTC)}")

# Update timestamp
os.makedirs(output_folder, exist_ok=True)
with open(timestamp_file, 'w') as f:
json.dump({"last_run": datetime.now(UTC).isoformat()}, f)

# Set permissions
SyftPermission.mine_with_public_read(email=client.email).ensure(output_folder)

if __name__ == "__main__":
main()

How it works:

  • The should_run() function checks if enough time has passed since the last execution:
    • If the timestamp file doesn't exist, it's the first run and it proceeds
    • Otherwise, it reads the last run time from the file and compares it with the current time - if enough time has passed, it proceeds with execution, otherwise skips it
  • An interval of 3600 seconds (1 hour) is defined for this example, but you can easily adjust the interval by changing the INTERVAL constant

Pattern 4: Long-Running Process (Service)

For Apps that need to run as a service and stay alive:

Example run.sh:

#!/bin/sh
set -e

# Configuration
PORT=8091
PID_FILE="pid.txt"

# Check if process is running
check_process() {
[ -n "$1" ] && ps -p "$1" > /dev/null 2>&1 &&
ps -p "$1" -o command= | grep -q "python main.py" && return 0
return 1
}

# Check for existing process
if [ -f "$PID_FILE" ] && check_process "$(cat "$PID_FILE")"; then
echo "Application already running"
exit 0
fi

# Setup environment
[ ! -d .venv ] && uv venv -p 3.9
. .venv/bin/activate
uv pip install -r requirements.txt --quiet

# Start service in background
echo "Starting service..."
nohup python3 main.py &
echo $! > "$PID_FILE"

# Verify startup
sleep 2
if check_process "$(cat "$PID_FILE")"; then
echo "Service started successfully (PID: $(cat "$PID_FILE"))"
else
echo "Failed to start service"
rm -f "$PID_FILE"
exit 1
fi

deactivate

How it works:

  • This script creates and manages a long-running background service that persists between Syftbox execution cycles
  • The check_process function verifies if a given process ID is running and is specifically our Python application
  • If the service is running, it simply exits without doing anything else
  • Otherwise, it starts the main Python application in background mode using nohup (which keeps it running after the script exits) and stores the process ID in a file for future reference
  • Verifies the service started successfully

This pattern enables you to run services like web servers, API endpoints, or monitoring processes that need to run continuously. Syftbox still attempts to execute this script every 10 seconds, but the script is designed to be idempotent - it only starts a new instance of the service if one isn't already running

Troubleshooting

If your App isn't running as expected:

  1. Check if your run.sh has execute permissions (chmod +x run.sh)
  2. Verify that your script is in the correct location (Syftbox/apps/your_app_name/)
  3. Look for error messages in Syftbox logs
  4. Test your script manually to ensure it works outside the Syftbox orchestration
  5. Check for leftover state files that might be preventing re-execution

If you're still having issues, reach out to the Syftbox community for help and support.