It turns out there is a simple command to package your Git repository as a zip file:

git archive -o builds/my-repo.zip --worktree-attributes HEAD

This includes all files in the repository, except the ones listed in the .gitignore file.

The complete documentation for the archive command can be found here.

Creates an archive of the specified format containing the tree structure for the named tree, and writes it out to the standard output. If is specified it is prepended to the filenames in the archive.

git archive behaves differently when given a tree ID versus when given a commit ID or tag ID. In the first case the current time is used as the modification time of each file in the archive. In the latter case the commit time as recorded in the referenced commit object is used instead. Additionally the commit ID is stored in a global extended pax header if the tar format is used; it can be extracted using git get-tar-commit-id. In ZIP files it is stored as a file comment.