A small operator-facing wrapper for running ArduPilot SITL in Docker or Colima.
SITLCTL manages two independent containers, so a Copter and Rover (or any other two supported vehicle selections) can run at the same time and connect to one MAVProxy session.
| Instance | Container | TCP endpoints | SYSIDs | State root |
|---|---|---|---|---|
1 |
ardupilot-sitl-1 |
5760, 5770, ... 5950 |
1..20 |
~/bin/logs/1 |
2 |
ardupilot-sitl-2 |
5960, 5970, ... 6150 |
21..40 |
~/bin/logs/2 |
Each instance can run one vehicle or a same-type swarm of up to 20 vehicles. Container names, Compose projects, host ports, ArduPilot instance offsets, SYSIDs, start locks, and state roots are separate. State is further isolated by vehicle and frame below each root. sitlctl logs reads the selected container's Docker output.
The launcher never stops Colima and never touches unrelated Docker workloads.
- Docker or Colima
- Docker Compose plugin (
docker compose) or standalonedocker-compose - Cached image
vanfleetdev/sitl-ardupilot:4.6.3
On macOS with Homebrew:
brew install colima docker docker-compose
brew services start colimagit clone https://github.com/vanfleet-dev/sitl-cli.git
cd sitl-cli
./install.shThe installer:
- keeps the cached image when present, or pulls it when missing;
- stages
sitlctl,docker-compose.yml,docker-entrypoint.sh, andlocations.txtas one bundle; - verifies the staged
sitlctl --helppath before replacing installed files; - installs the complete bundle under
~/bin; - removes the old
~/bin/sitlcommand only after the verified bundle is installed.
Ensure ~/bin is in PATH:
export PATH="$HOME/bin:$PATH"sitlctl start <1|2> <vehicle> [options]
sitlctl stop <1|2|all>
sitlctl status [1|2|all]
sitlctl logs <1|2>
sitlctl shell <1|2>
sitlctl --help
Supported vehicles:
planequadplanecoptercopter-hexacopter-octacopter-triroverrover-skid
Helicopter is not advertised because the pinned image does not contain the separate arducopter-heli binary required by ArduPilot's frame metadata. The known heli, heli-gas, heli-dual, and heli-blade360 frame overrides are rejected before Docker startup.
Start options:
--swarm <1..20>
--frame <name>
--wipe
--location <name>
--speedup <positive integer>
--offset-line <heading,distance>
Unknown commands, options, and location names fail before Docker startup instead of being ignored.
sitlctl start 1 copter
sitlctl start 2 rover
sitlctl status allConnect both links:
mavproxy.py \
--master=tcp:localhost:5760 \
--master=tcp:localhost:5960Stop one vehicle without affecting the other:
sitlctl stop 1
sitlctl status 2Stop everything owned by this tool:
sitlctl stop allsitlctl start 1 copter --swarm 5 --offset-line 90,10
sitlctl start 2 rover --swarm 3 --location CMACInstance 1 receives SYSIDs 1..5 at TCP ports 5760..5800. Instance 2 receives SYSIDs 21..23 at TCP ports 5960..5980.
Connect each published endpoint separately:
mavproxy.py \
--master=tcp:localhost:5760 \
--master=tcp:localhost:5770 \
--master=tcp:localhost:5960 \
--master=tcp:localhost:5970The default is MAVPROXY_USGS1M. Select another exact entry from locations.txt with:
sitlctl start 1 rover --location CMACAll MAVLink endpoints are published on host loopback only (127.0.0.1).
Normal starts never pull or replace the image. If the configured image is absent, sitlctl exits and prints the explicit docker pull command.
Override the image for a deliberate test:
SITL_IMAGE=example/image:tag sitlctl start 1 copterbash -n sitlctl install.sh docker-entrypoint.sh
python3 -m unittest discover -s tests -v
docker compose -f docker-compose.yml configRuntime acceptance still requires real ArduPilot heartbeats on every requested MAVLink link; container startup alone is not sufficient.
MIT. See LICENSE.