long

If you've ever manually released a software package, you know the drill: update version numbers in multiple files, generate a changelog, create git tags, build and sign the package, upload to various distribution platforms... and then do it all over again when you need a hotfix. It's tedious, error-prone, and frankly, not a great use of your time.

Automating Nextcloud App Releases with GitHub Actions: A Complete Guide

When I was building The Search Page app for Nextcloud, I decided to automate this entire process using GitHub Actions. After some trial and error (okay, a lot of trial and error), I ended up with a robust three-workflow system that handles everything from version bumping to app store publishing. In this post, I'll walk you through how it works and how you can set up something similar for your own projects.

What We're Automating

The release system I built handles all the boring but critical parts of shipping software:

  • Version management based on your commit messages (using conventional commits)
  • Automated changelog generation that actually makes sense
  • Git tag creation without manual intervention
  • Building and code signing the app package
  • Dual publishing to both GitHub Releases and the Nextcloud App Store
  • Pre-release testing capabilities for those "let's make sure this works first" moments

The beauty of this setup is that once you push a commit to main, the system takes over. No manual steps, no forgotten files, no version mismatches.

The Three-Workflow Architecture

The system consists of three interconnected workflows that work together like a well-oiled machine:

1. Version Bump Workflow

This is where the magic starts. Every time you push to main, the Version Bump workflow springs into action. It looks at your commit messages, figures out if you've added features (that's a minor bump), fixed bugs (patch bump), or made other changes, and automatically increments the version number.

Here's what happens under the hood:

First, it checks if the last commit was made by the bot itself. This prevents the dreaded infinite loop where the bot commits a version bump, which triggers the workflow, which creates another version bump, which... you get the idea. Been there, done that, learned my lesson.

Next, it reads your current version from appinfo/info.xml, finds the last git tag, and analyzes all the commits since then. The workflow uses conventional commit format to determine what kind of version bump is needed:

  • Commits starting with feat: trigger a minor version bump (2.0.1 → 2.1.0)
  • Commits with fix: or chore: trigger a patch bump (2.0.1 → 2.0.2)
  • If there are no conventional commits, the workflow simply skips

The workflow then updates your appinfo/info.xml file with the new version, generates a clean changelog entry with all your commit messages (minus the conventional commit prefixes), and prepends it to your CHANGELOG.md. Finally, it creates a commit with the message "chore: bump version to X.Y.Z" and pushes both the commit and a new git tag.

Pro tip: The changelog generation is surprisingly smart. It creates a nicely formatted entry with a comparison link between versions, making it easy for users to see exactly what changed:

## [v2.1.0](https://github.com/yourrepo/compare/v2.0.1...v2.1.0)

- Add dark mode support
- Fix search timeout issue
- Update dependencies

2. Production Release Workflow

Once the Version Bump workflow completes successfully, the Production Release workflow kicks in automatically. This is where your code actually gets built and shipped to users.

The workflow starts with a critical validation step: it checks that the git tag version matches what's in appinfo/info.xml. This might seem paranoid, but trust me, version mismatches are a nightmare to debug when users report issues with "version 2.0.2" but your git history shows 2.0.1.

After validation passes, the workflow sets up a complete Nextcloud environment (I'm using stable31 with PHP 8.2 and SQLite for testing). Then it builds your app by running make, which handles all the composer dependencies, npm packages, and frontend compilation.

The really interesting part is the signing and packaging. The make appstore target does several things:

  1. Sets up signing certificates from GitHub secrets
  2. Filters files using rsync to include only distribution files (no source files, tests, or development cruft)
  3. Signs the app using Nextcloud's code signing tools
  4. Creates a tarball with everything packaged up nicely
  5. Generates a SHA-512 signature for verification

Finally, the workflow publishes the signed tarball to two places: your GitHub release (so people can download it directly) and sends the link to the Nextcloud App Store (so it shows up in the Nextcloud web interface).

3. Pre-Release Workflow

The pre-release workflow is your safety net. Before pushing a production release, you can create a pre-release tag (like v2.1.0-pre1) to test everything in a real-world environment.

The workflow is similar to the production release but with a few key differences:

  • It validates that the base version (2.1.0 in v2.1.0-pre1) matches your info.xml
  • It publishes to GitHub as a pre-release (not a full release)
  • It uploads to the Nextcloud App Store as a "nightly" build, which keeps it separate from stable releases

Real-World Usage Examples

Let me walk you through how this actually works in practice.

Scenario 1: A Simple Bug Fix

You've just fixed a critical bug where search was timing out for large result sets. Here's what happens:

  1. You commit your fix: git commit -m "fix: resolve search timeout for large result sets"
  2. You push to main: git push origin main
  3. The Version Bump workflow immediately triggers, detects the fix: prefix, bumps the version from 2.0.1 to 2.0.2, updates the changelog, and creates the v2.0.2 tag
  4. The Production Release workflow then builds, signs, and publishes your app

Total time: About 5-10 minutes, completely automatic. You can literally go grab a coffee while your fix deploys itself.

Scenario 2: Adding a New Feature

When you're adding something substantial, like dark mode support, you might want more control over timing:

  1. You work on your feature branch and merge it to main
  2. Instead of waiting for the automatic bump, you go to GitHub Actions → Version Bump → Run workflow
  3. You select "minor" as the bump type (this will go from 2.0.2 to 2.1.0)
  4. Everything else happens automatically

Scenario 3: Testing Before Production

This is where pre-releases shine. Let's say you've implemented a major new feature and you want to test it thoroughly:

  1. You prepare version 2.1.0 in your info.xml and commit it to main
  2. You create a pre-release tag: git tag v2.1.0-pre1 && git push origin v2.1.0-pre1
  3. The Pre-Release workflow publishes it as a nightly build
  4. You test thoroughly with your beta testers
  5. When everything looks good, you either let the Version Bump workflow create v2.1.0 automatically, or you create it manually

Scenario 4: Emergency Hotfix

Production is down, users are affected, and you need to get a fix out now:

  1. Create a hotfix branch from the problematic tag: git checkout -b hotfix/2.0.3 v2.0.2
  2. Apply your fix and manually update appinfo/info.xml from 2.0.2 to 2.0.3
  3. Commit and tag: git commit -m "fix: critical security issue" && git tag v2.0.3
  4. Push the tag: git push origin v2.0.3
  5. The Production workflow triggers immediately from the tag push
  6. Once released, merge the hotfix back to main: git checkout main && git merge hotfix/2.0.3

Setting Up the Secrets

For all this to work, you'll need to configure a few GitHub secrets in your repository settings:

Code Signing Certificates

Nextcloud requires apps to be code-signed. You'll need to generate a certificate pair and add them as secrets:

  • APP_PRIVATE_KEY: Your RSA private key (including the -----BEGIN RSA PRIVATE KEY----- header and footer)
  • APP_PUBLIC_CRT: Your public certificate

Important: Make sure there's no extra whitespace or line breaks in these secrets. The workflows write them directly to files, and any formatting issues will cause signing to fail.

App Store Token

To publish to the Nextcloud App Store, you need an API token:

This token allows the workflow to automatically publish new versions. You should rotate it periodically for security.

Common Issues and How to Fix Them

"Version mismatch!" Error

Symptom: The release workflow fails with a message like "Tag version: 2.0.2, info.xml version: 2.0.1"

What happened: Your git tag and appinfo/info.xml are out of sync. This usually happens when someone manually creates a tag without updating the info file first.

Fix:

# Delete the incorrect tag
git tag -d v2.0.2
git push origin :refs/tags/v2.0.2

# Update appinfo/info.xml to the correct version
# Then let the Version Bump workflow create the tag properly

Workflow Not Triggering

Symptom: You push to main, but nothing happens.

What happened: The Version Bump workflow only runs if there are conventional commits since the last tag. If you pushed changes without feat:, fix:, or chore: prefixes, the workflow correctly skips.

Fix: If you actually want a version bump, either use conventional commit format or manually trigger the workflow via GitHub Actions UI.

App Store Upload Fails

Symptom: The workflow completes but the app doesn't show up in the Nextcloud App Store.

Common causes:

  1. Invalid or expired APPSTORE_TOKEN (regenerate at apps.nextcloud.com)
  2. App signature is invalid (check your signing certificates)
  3. Version already exists (the App Store won't let you overwrite existing releases)

Fix: Check the workflow logs for the specific error, verify your token and certificates, and make sure you're not trying to re-publish an existing version.

Pre-Release Shows as Production

Symptom: Your pre-release appears in the App Store as a stable release instead of a nightly.

What happened: You used a tag pattern that doesn't match v*-pre*, so the production workflow triggered instead.

Fix: Pre-release tags must follow this exact pattern:

# Correct (triggers pre-release workflow)
git tag v2.1.0-pre1

# Wrong (triggers production workflow)
git tag v2.1.0-preview
git tag v2.1.0-beta

Best Practices I've Learned

After running this system for several release cycles, here are my recommendations:

1. Embrace Conventional Commits

It might feel like extra work at first, but conventional commits make versioning completely automatic. Your commit messages become the source of truth for what changed and how significant those changes are. Plus, your changelog writes itself.

2. Monitor Your Workflows

Set up notifications for workflow failures. The GitHub Actions tab is your friend. Check it after pushing to main to verify the Version Bump completed successfully and the Production Release published correctly.

3. Validate Locally When Possible

Before pushing, you can test your build locally:

make clean
make
make appstore  # Requires signing keys

This catches build issues before they hit CI and saves you time debugging in the cloud.

4. Keep Secrets Fresh

Rotate your APPSTORE_TOKEN periodically. I do this quarterly as part of my security maintenance. It's much better to do this proactively than to discover your token expired when you're trying to push an emergency fix.

5. Document Breaking Changes

For major version bumps, make sure you update your README and provide migration guides. The automatic changelog is great for listing changes, but significant version bumps deserve extra documentation.

Customizing for Your Project

The workflows are designed to be adaptable. Here are some common modifications:

Modifying Build Steps

The workflows use whatever build commands you define in your Makefile or package.json. Want to add linting? Update your make targets. Need to compile TypeScript? Add it to your npm scripts. The workflows will automatically use your updated build process.

The Bottom Line

Setting up automated releases might seem like overkill for a small project, but the time investment pays off quickly. I've probably saved dozens of hours by not having to manually manage versions, generate changelogs, and remember all the steps to publish a release.

More importantly, automation eliminates human error. I no longer worry about forgetting to update a version number or accidentally skipping the code signing step. The workflows handle it consistently, every single time.

If you're building a Nextcloud app (or really any software project), I highly recommend investing the time to set up something similar. Your future self will thank you when you can fix a critical bug and have it deployed to users in less time than it takes to finish your coffee.

Additional Resources

Want to dive deeper? Here are some helpful links:


Have questions about setting up automated releases? Run into issues with your GitHub Actions workflows? Feel free to reach out.

About me

Stuff I do with my brain, my fingers and an editor:

(Front-end) development - typescript, Vue, React, svelte(kit) web architectures web performance API design pragmatic SEO security Accessibility (A11y) front-end dev recruitment and probably more...

Feel free to , check out my open source projects, or just read the things I write on this site.