Force `shards build` to use musl on Ubuntu?

Is there a way to force shards build to use musl on an Ubuntu system in one go, or do I need to cross compile and link in a separate step?

Neither shards nor crystal have much to do for this, actually. You can tell it to target a musl environment via the --target option. That’s it.
The rest is up to the linker. It needs to be able to link to musl libc and have the required libraries available.
Ubuntu’s default cannot do that. So you need to use one that does.
I can’t give much advice on that. However I believe zig cc should be able to do that. And I’ve successfully linked a Crystal program against musl from a glib distro in an environment managed by nix. There might be other options that I’m not aware of.

Of course you could also link (or compile and link) in an environment that is based on musl libc, e.g. run AlpineLinux in a container or chroot.

Check this.

You need a musl-libc toolchain such as https://musl.cc/ or zig cc for example, but you’ll have to compile every required library using this toolchain (bdwgc, pcre2, libevent2, …), then compile a binary with a command such as (you’ll need to specify some -Lpath as link flags):

$ CC=/path/to/toolchain/cc crystal build \
  --target=x86_64-linux-musl \
  --link-flags="..."

If you’re already on Ubuntu it might be simpler to compile a static executable in a Docker image (e.g. either the official alpine or the crystallang/crystal:alpine images) directly.