diff --git a/Library/Homebrew/bintray.rb b/Library/Homebrew/bintray.rb
index bb76749a2f7e3706748f024c3d0812e95c0856e6..5d8b89ed5aedc7ddc18e9e423a7e6f3c5da6bb77 100644
--- a/Library/Homebrew/bintray.rb
+++ b/Library/Homebrew/bintray.rb
@@ -148,11 +148,7 @@ class Bintray
     end
   end
 
-  def upload_bottle_json(json_files, publish_package: false, warn_on_error: false)
-    bottles_hash = json_files.reduce({}) do |hash, json_file|
-      hash.deep_merge(JSON.parse(IO.read(json_file)))
-    end
-
+  def upload_bottles(bottles_hash, publish_package: false, warn_on_error: false)
     formula_packaged = {}
 
     bottles_hash.each do |formula_name, bottle_hash|
diff --git a/Library/Homebrew/dev-cmd/pr-upload.rb b/Library/Homebrew/dev-cmd/pr-upload.rb
index aa83627f63736011c0e89205d8bd68e5f708467a..08e59b9c2e8bc77f8b25859134199f5df0612faf 100644
--- a/Library/Homebrew/dev-cmd/pr-upload.rb
+++ b/Library/Homebrew/dev-cmd/pr-upload.rb
@@ -11,7 +11,7 @@ module Homebrew
       usage_banner <<~EOS
         `pr-upload` [<options>]
 
-        Apply the bottle commit and publish bottles to Bintray.
+        Apply the bottle commit and publish bottles to Bintray or GitHub Releases.
       EOS
       switch "--no-publish",
              description: "Apply the bottle commit and upload the bottles, but don't publish them."
@@ -30,30 +30,37 @@ module Homebrew
     end
   end
 
-  def check_bottled_formulae(json_files)
-    hashes = json_files.reduce({}) do |hash, json|
-      hash.deep_merge(JSON.parse(IO.read(json)))
-    end
-
-    hashes.each do |name, hash|
-      formula_path = HOMEBREW_REPOSITORY/hash["formula"]["path"]
+  def check_bottled_formulae(bottles_hash)
+    bottles_hash.each do |name, bottle_hash|
+      formula_path = HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"]
       formula_version = Formulary.factory(formula_path).pkg_version
-      bottle_version = PkgVersion.parse hash["formula"]["pkg_version"]
+      bottle_version = PkgVersion.parse bottle_hash["formula"]["pkg_version"]
       next if formula_version == bottle_version
 
       odie "Bottles are for #{name} #{bottle_version} but formula is version #{formula_version}!"
     end
   end
 
+  def github_releases?(bottles_hash)
+    @github_releases ||= bottles_hash.values.all? do |bottle_hash|
+      root_url = bottle_hash["bottle"]["root_url"]
+      url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX
+      _, _, _, tag = *url_match
+
+      tag
+    end
+  end
+
   def pr_upload
     args = pr_upload_args.parse
 
-    bintray_org = args.bintray_org || "homebrew"
-    bintray = Bintray.new(org: bintray_org)
-
     json_files = Dir["*.json"]
     odie "No JSON files found in the current working directory" if json_files.empty?
 
+    bottles_hash = json_files.reduce({}) do |hash, json_file|
+      hash.deep_merge(JSON.parse(IO.read(json_file)))
+    end
+
     bottle_args = ["bottle", "--merge", "--write"]
     bottle_args << "--verbose" if args.verbose?
     bottle_args << "--debug" if args.debug?
@@ -62,14 +69,51 @@ module Homebrew
     bottle_args += json_files
 
     if args.dry_run?
+      service = if github_releases?(bottles_hash)
+        "GitHub Releases"
+      else
+        "Bintray"
+      end
       puts "brew #{bottle_args.join " "}"
-      puts "Upload bottles described by these JSON files to Bintray:\n  #{json_files.join("\n  ")}"
+      puts "Upload bottles described by these JSON files to #{service}:\n  #{json_files.join("\n  ")}"
+      return
+    end
+
+    check_bottled_formulae(bottles_hash)
+
+    safe_system HOMEBREW_BREW_FILE, *bottle_args
+
+    if github_releases?(bottles_hash)
+      # Handle uploading to GitHub Releases.
+      bottles_hash.each_value do |bottle_hash|
+        root_url = bottle_hash["bottle"]["root_url"]
+        url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX
+        _, user, repo, tag = *url_match
+
+        # Ensure a release is created.
+        release = begin
+          GitHub.get_release user, repo, tag
+          odebug "Existing GitHub release \"#{tag}\" found"
+        rescue GitHub::HTTPNotFoundError
+          odebug "Creating new GitHub release \"#{tag}\""
+          GitHub.create_or_update_release user, repo, tag
+        end
+
+        # Upload bottles as release assets.
+        bottle_hash["bottle"]["tags"].each_value do |tag_hash|
+          remote_file = tag_hash["filename"]
+          local_file = tag_hash["local_filename"]
+          odebug "Uploading #{remote_file}"
+          GitHub.upload_release_asset user, repo, release["id"], local_file: local_file, remote_file: remote_file
+        end
+      end
     else
-      check_bottled_formulae(json_files)
-      safe_system HOMEBREW_BREW_FILE, *bottle_args
-      bintray.upload_bottle_json(json_files,
-                                 publish_package: !args.no_publish?,
-                                 warn_on_error:   args.warn_on_upload_failure?)
+      # Handle uploading to Bintray.
+      bintray_org = args.bintray_org || "homebrew"
+      bintray = Bintray.new(org: bintray_org)
+      bintray.upload_bottles(bottles_hash,
+                             publish_package: !args.no_publish?,
+                             warn_on_error:   args.warn_on_upload_failure?)
     end
   end
 end
diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb
index 3159ea53883c7e312381f22820af4df8aba4f736..227e581f8ea4957e4f4a378057a1eaba1ab19558 100644
--- a/Library/Homebrew/global.rb
+++ b/Library/Homebrew/global.rb
@@ -112,6 +112,8 @@ HOMEBREW_PULL_API_REGEX =
   %r{https://api\.github\.com/repos/([\w-]+)/([\w-]+)?/pulls/(\d+)}.freeze
 HOMEBREW_PULL_OR_COMMIT_URL_REGEX =
   %r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})].freeze
+HOMEBREW_RELEASES_URL_REGEX =
+  %r{https://github\.com/([\w-]+)/([\w-]+)?/releases/download/(.+)}.freeze
 
 require "PATH"
 
diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb
index f14b4e6387e57d1ddb2b17a30c81960edf5199f4..2ca8dc31985e13b6256bab78b5a0b779570d6581 100644
--- a/Library/Homebrew/utils/github.rb
+++ b/Library/Homebrew/utils/github.rb
@@ -175,7 +175,7 @@ module GitHub
     end
   end
 
-  def open_api(url, data: nil, request_method: nil, scopes: [].freeze, parse_json: true)
+  def open_api(url, data: nil, data_binary_path: nil, request_method: nil, scopes: [].freeze, parse_json: true)
     # This is a no-op if the user is opting out of using the GitHub API.
     return block_given? ? yield({}) : {} if Homebrew::EnvConfig.no_github_api?
 
@@ -200,6 +200,11 @@ module GitHub
       end
     end
 
+    if data_binary_path.present?
+      args += ["--data-binary", "@#{data_binary_path}"]
+      args += ["--header", "Content-Type: application/gzip"]
+    end
+
     headers_tmpfile = Tempfile.new("github_api_headers", HOMEBREW_TEMP)
     begin
       if data
@@ -468,6 +473,33 @@ module GitHub
                   scopes:         CREATE_ISSUE_FORK_OR_PR_SCOPES)
   end
 
+  def get_release(user, repo, tag)
+    url = "#{API_URL}/repos/#{user}/#{repo}/releases/tags/#{tag}"
+    open_api(url, request_method: :GET)
+  end
+
+  def create_or_update_release(user, repo, tag, id: nil, name: nil, draft: false)
+    url = "#{API_URL}/repos/#{user}/#{repo}/releases"
+    method = if id
+      url += "/#{id}"
+      :PATCH
+    else
+      :POST
+    end
+    data = {
+      tag_name: tag,
+      name:     name || tag,
+      draft:    draft,
+    }
+    open_api(url, data: data, request_method: method, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
+  end
+
+  def upload_release_asset(user, repo, id, local_file: nil, remote_file: nil)
+    url = "https://uploads.github.com/repos/#{user}/#{repo}/releases/#{id}/assets"
+    url += "?name=#{remote_file}" if remote_file
+    open_api(url, data_binary_path: local_file, request_method: :POST, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
+  end
+
   def get_artifact_url(user, repo, pr, workflow_id: "tests.yml", artifact_name: "bottles")
     scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES
     base_url = "#{API_URL}/repos/#{user}/#{repo}"
diff --git a/docs/Manpage.md b/docs/Manpage.md
index bb80e26cf141324319287b4aced5b59da53ba6d3..531b47decb5dabcb9ae37da914ef5bf02688ce31 100644
--- a/docs/Manpage.md
+++ b/docs/Manpage.md
@@ -1113,7 +1113,7 @@ Requires write access to the repository.
 
 ### `pr-upload` [*`options`*]
 
-Apply the bottle commit and publish bottles to Bintray.
+Apply the bottle commit and publish bottles to Bintray or GitHub Releases.
 
 * `--no-publish`:
   Apply the bottle commit and upload the bottles, but don't publish them.
diff --git a/manpages/brew.1 b/manpages/brew.1
index ff066df00d1425b7edaae0e7ededf78d07563553..d9da1fb564ee75f47c95dc40c57a6b39f1a2569f 100644
--- a/manpages/brew.1
+++ b/manpages/brew.1
@@ -1547,7 +1547,7 @@ Use the specified \fIURL\fR as the root of the bottle\'s URL instead of Homebrew
 Use the specified Bintray repository to automatically mirror stable URLs defined in the formulae (default: \fBmirror\fR)\.
 .
 .SS "\fBpr\-upload\fR [\fIoptions\fR]"
-Apply the bottle commit and publish bottles to Bintray\.
+Apply the bottle commit and publish bottles to Bintray or GitHub Releases\.
 .
 .TP
 \fB\-\-no\-publish\fR