# vmactions/dragonflybsd-vm puts a real DragonFlyBSD in your GitHub Actions job

> A QEMU powered action that boots DragonFlyBSD as a build target on an Ubuntu runner, so a project whose native CI matrix stops at Ubuntu, Windows and MacOS can still be compiled and tested on DragonFlyBSD. The interesting parts are the sync methods that do not work there and the sh rule that overrides the guest login shell.

**vmactions/dragonflybsd-vm** — Test your code in a real DragonFly BSD virtual machine on GitHub Actions -- QEMU-powered, with SSH and two-way folder sync

- Repository: https://github.com/vmactions/dragonflybsd-vm
- Website: https://vmactions.org
- Stars: 25 · Forks: 2
- Language: JavaScript
- License: MIT
- Published: 2026-08-27 · Updated: 2026-08-27 · Language: en
- Canonical page: https://hysenlabs.com/projects/vmactions-dragonflybsd-vm

## sshfs is absent from the DragonFlyBSD table, and the reason is not an oversight

The release table for this action lists three DragonFlyBSD versions, 6.4.0, 6.4.1 and 6.4.2, and every one of them is available on x86_64 under four sharing methods: rsync, scp, nfs and tar. A fifth method you will see referenced for other systems in this family, sshfs, appears nowhere in that table, and the reason given is specific rather than a missing feature. On DragonFlyBSD the sshfs FUSE mount is read-only in practice: the guest can read the shared directory, but writing a file back into the mount fails. That single limitation is what removes FUSE from the menu, since a read-only mount is close to useless for a build that wants to write artifacts. The constraint lives in the guest image rather than in your workflow file, so there is no setting that brings sshfs back on this operating system.

## sync: no and copyback: false do opposite things

One option controls how code reaches the VM. The default is rsync, the same option accepts sshfs, nfs or scp as alternatives, and it also accepts the literal value no, in which case files are not moved into the VM at all. A second option, copyback, controls the return direction, and setting it to false stops files being copied from the VM back to the host. The important detail is which sync methods that second option applies to. It only has an effect for the copy-based methods: rsync, scp, tar and 9p. On sshfs and nfs it does nothing at all, because those two are live mounts where guest and host already share one filesystem and nothing is ever copied back. Combining the two options on DragonFlyBSD gives you a narrower set than the option names suggest: three usable copy-based methods, one live mount, and an option that silently does nothing on that mount.

```
...
    - name: Test
      id: test
      uses: vmactions/dragonflybsd-vm@v1
      with:
        sync: rsync
        copyback: false

...
```

## Each image is an unattended ISO install captured as qcow2

The guest images are not curated by hand. They are produced automatically in the GitHub Actions of a separate builder repository under the anyvm-org organisation. The build downloads the official DragonFlyBSD installer ISO, boots it inside QEMU, runs the installation unattended, enables ssh, pre-installs the packages listed in the build conf, and exports the installed disk as a compressed qcow2 image. The upstream media comes from the DragonFlyBSD ISO mirror, with the project download page as the human facing entry point. Two consequences follow from that pipeline. First, packages baked into the image are whatever the conf file names, so anything else your build needs has to be installed in your own prepare step rather than assumed present. Second, because ssh is switched on during the image build, the action's transport layer assumes it is already listening in the guest. Neither of those is negotiable from the workflow side.

## The guest architecture changes, the runner does not

Guest architecture and runner architecture are two different settings, and conflating them is the documented mistake. The VM uses x86_64 by default, and the arch option changes it, with aarch64 given as the working example. When you run with aarch64 the host runner should still be the ordinary x86_64 one, written as runs-on: ubuntu-latest, and choosing the arm flavour of the hosted runner is discouraged because it is much slower. The same value shows up in the migration note for people arriving from the v0 version of this action, which tells them to change runs-on to ubuntu-latest. On the pinning side, the recommended reference is the major version tag, with the full version given as the alternative. The action itself is a JavaScript program with its manifest in action.yml and its entry point in index.js; its package manifest carries the same version number as the release tags.

```
...
    - name: Test
      id: test
      uses: vmactions/dragonflybsd-vm@v1
      with:
        arch: aarch64
...
```

## prepare and run both execute under sh, whatever the login shell is

Every script you hand this action is run by sh inside the VM, and the docs state that this holds regardless of what the guest's default login shell happens to be. That single rule covers both script inputs, the prepare block and the run block, which is why a workflow can look identical across operating systems even when the images disagree about shells. The practical consequence is that syntax belonging to a different interpreter will not behave the way it does in an interactive session, even if the image's login shell would accept it. If a step needs a particular interpreter or a particular feature, invoke it explicitly inside the run block rather than relying on the shebang of the file you execute or on the shell you would get after logging in by hand.

## GITHUB_ names, CI=true and your checkout all cross the boundary

Environment handling has three moving parts. A space separated list in the envs option names the variables of your own that should enter the VM, and in the sample workflow that list picks up two names, one drawn from repository secrets and one written inline. Beyond that selection, every variable whose name begins with GITHUB_ is passed in automatically, together with a CI variable set to true. The filesystem is aligned to match: the working directory for the run block is the same path as on the runner, and the entire source tree from the host is brought into the VM. The net effect is that a script written for the runner finds the same directory layout and the same default variables inside the guest. Anything neither listed in envs nor prefixed with GITHUB_ stays behind, which means a secret has to be named explicitly to become visible in DragonFlyBSD.

```
name: Test

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    name: A job to run test in DragonflyBSD
    env:
      MYTOKEN : ${{ secrets.MYTOKEN }}
      MYTOKEN2: "value2"
    steps:
    - uses: actions/checkout@v7
    - name: Test in DragonflyBSD
      id: test
      uses: vmactions/dragonflybsd-vm@v1
      with:
        envs: 'MYTOKEN MYTOKEN2'
        prepare: |
          pkg install -y socat

        run: |
          pwd
          ls -lah
          whoami
          env
          uname -a
          echo "OK"
```

## A partial release string resolves to the newest match or fails loudly

The guest OS version is pinned by default to a specific point release, and the release option moves it. You can write a full version string, or you can write only the leading dot separated part of one, in which case the newest release starting with it is used so the workflow does not have to be edited for every point release. More parts narrow the search: the two part form runs the newest release in that line, and the single part form runs the newest release across the whole major line. The caveat is the one that matters for CI. Each part you give has to match in full, so a release that does not exist fails the job instead of quietly falling back to another image. A typo becomes a red build rather than a silent upgrade to a guest you did not ask for.

```
...
    - name: Test
      id: test
      uses: vmactions/dragonflybsd-vm@v1
      with:
        release: "6"
...
```

## Memory starts at 6144MB, cores default to all of them, ports are opt-in

Resource defaults are generous and adjustable through options. The VM starts with 6144MB of memory, and the mem option sets a different size in megabytes, with 4096 given as the example. Processor handling works the opposite way from a cap: by default the VM takes all the CPU cores of the host runner, and the cpu option sets a smaller count, with three given as the example. Networking back to the runner is separate and entirely opt-in through the nat option, which maps host ports to guest ports as quoted string pairs. The sample maps a plain HTTP host port onto port 80 in the guest and an HTTPS host port onto 443, and shows a udp prefix in front of a pair when you want UDP rather than TCP. Nothing is forwarded unless you declare it, so a service started inside DragonFlyBSD has no path back to the runner until a mapping exists.

```
...
    - name: Test
      id: test
      uses: vmactions/dragonflybsd-vm@v1
      with:
        mem: 4096
...
```

## Conclusion

Take this action when your project actually has to build on DragonFlyBSD and the hosted runners cannot give you that OS, and when you can live with copy-based file sharing rather than a live mount. Two things to verify before the first run: that your guest version string exists, because a nonexistent release fails the job on purpose, and that every dependency your build needs is installed in your own prepare step, since the image only pre-installs what the build conf names. If you need an arm runner or a real shared filesystem, this is the wrong tool.

## FAQ

### Can I run virtual machines on FreeBSD with the vmactions approach?

This action is scoped to DragonFlyBSD, not FreeBSD, and it boots the guest under QEMU on a hosted GitHub Actions runner. The host runner is written as runs-on: ubuntu-latest even when the guest architecture is set to aarch64, so the repository documents no FreeBSD host setup.

### What is BSD used for, in the case of vmactions/dragonflybsd-vm?

Here DragonFlyBSD is a build and test target for projects whose hosted runners only offer Ubuntu, Windows and MacOS. The action provides releases 6.4.0, 6.4.1 and 6.4.2 on x86_64, with rsync, scp, nfs and tar as the ways to share code into the guest.

### Why is BSD not popular as Linux, according to this project?

The project says nothing about adoption. What it documents is that the GitHub workflow runners support Ubuntu, Windows and MacOS, that DragonFlyBSD therefore needs a virtual machine inside the job, and that the guest runs a headless installation with ssh enabled and code shared by one of four listed methods.

## Sources

- [Official documentation](https://vmactions.org)
- [Official README](https://github.com/vmactions/dragonflybsd-vm#readme)
- [Project repository](https://github.com/vmactions/dragonflybsd-vm)
- [Release notes](https://github.com/vmactions/dragonflybsd-vm/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/vmactions-dragonflybsd-vm
