Introduction
Lifecycle scripts allow you to react to VM state changes by running a shell script on startup or shutdown. Startup scripts run after the network is accessible, while shutdown scripts run when the VM receives an ACPI shutdown signal and have up to 90 seconds to complete before the VM is forcibly terminated. All scripts run as root.
While lifecycle scripts can be configured during VM creation via the UI or CLI, this guide covers how to add or update them on a VM that is already running.
Prerequisites
- An Existing Crusoe Cloud VM
- SSH Access to the VM
Instructions
-
SSH into your VM
$ ssh ubuntu@<External IP>
-
Navigate to the Lifecycle Scripts Directory
Crusoe lifecycle scripts live at
/usr/local/bin/crusoe/. This directory contains two files —startupandshutdown— which correspond to the startup and shutdown scripts respectively.$ ls /usr/local/bin/crusoe/
-
Edit or Create the Script
Use your preferred editor to modify or create the startup script. The example below uses
nano:$ sudo nano /usr/local/bin/crusoe/startup
All scripts must begin with
#!/bin/bash. Below is a simple example startup script that logs the time and checks network connectivity:#!/bin/bash LOGFILE="/var/log/my-startup.log" echo "=== Startup Script ===" | tee -a $LOGFILE echo "Timestamp: $(date)" | tee -a $LOGFILE if ping -c 1 -W 5 8.8.8.8 &>/dev/null; then echo "Network: REACHABLE" | tee -a $LOGFILE else echo "Network: NOT REACHABLE" | tee -a $LOGFILE fi echo "=== Script Complete: $(date) ===" | tee -a $LOGFILE -
Save the File and Verify It
$ sudo cat /usr/local/bin/crusoe/startup
-
Test the Script Manually Before Rebooting (Optional but Recommended)
$ sudo bash /usr/local/bin/crusoe/startup
Then check your log file to confirm it ran as expected:
$ cat /var/log/my-startup.log
-
Reboot to Confirm the Script Runs on Startup
$ sudo reboot
After the VM comes back up, SSH back in and verify your log file was written correctly:
$ cat /var/log/my-startup.log
Example
A successful startup script run should produce output similar to the following in your log file:
=== Startup Script === Timestamp: Wed Jun 24 22:12:14 UTC 2026 Network: REACHABLE === Script Complete: Wed Jun 24 22:12:31 UTC 2026 ===
Script Execution Order
Lifecycle scripts execute sequentially from top to bottom — each command must complete before the next one begins. This is important when your script has steps that depend on a previous step finishing first, such as a service being ready before another one starts, or a package install completing before running a configuration command.
If a step needs to wait on something before proceeding, there are two common approaches:
Option 1 — Fixed wait with sleep
Use sleep to pause execution for a set number of seconds. This is simple but not always reliable since you're guessing how long something will take:
#!/bin/bash echo "Starting service..." systemctl start my-service # Wait 10 seconds for the service to fully initialize sleep 10 echo "Running post-start configuration..." my-config-command
Option 2 — Conditional wait loop
A more reliable approach is to actively poll for a condition before proceeding:
#!/bin/bash
echo "Waiting for my-service to be ready..."
until systemctl is-active --quiet my-service; do
echo "Service not ready yet, retrying in 5s..."
sleep 5
done
echo "Service is ready, continuing..."
my-config-command⚠️ Warning: For shutdown scripts, keep in mind the 90-second time limit. Avoid long
sleeptimers or unbounded wait loops in shutdown scripts, as the VM will be forcibly terminated once the limit is reached regardless of where the script is in execution.
Limitations
- Scripts must start with
#!/bin/bash - Scripts must be less than 64 kB in size
- Scripts may only contain ASCII characters
- Shutdown scripts have a maximum of 90 seconds to complete before the VM is forcibly terminated