diff --git a/CMakeLists.txt b/CMakeLists.txt
index ba46ec367..dc960f3bd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -136,6 +136,8 @@ project( Audacity )
 # Load our functions/macros
 include( AudacityFunctions )
 
+set_from_env(AUDACITY_ARCH_LABEL) # e.g. x86_64
+
 # Allow user to globally set the library preference
 cmd_option( ${_OPT}lib_preference
             "Library preference [system (if available), local]"
diff --git a/cmake-proxies/cmake-modules/Package.cmake b/cmake-proxies/cmake-modules/Package.cmake
index ccbfdc724..f6bfed833 100644
--- a/cmake-proxies/cmake-modules/Package.cmake
+++ b/cmake-proxies/cmake-modules/Package.cmake
@@ -1,4 +1,3 @@
-
 set(CPACK_PACKAGE_VERSION_MAJOR "${AUDACITY_VERSION}") # X
 set(CPACK_PACKAGE_VERSION_MINOR "${AUDACITY_RELEASE}") # Y
 set(CPACK_PACKAGE_VERSION_PATCH "${AUDACITY_REVISION}") # Z
@@ -13,30 +12,53 @@ if(NOT AUDACITY_BUILD_LEVEL EQUAL 2)
    set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}+${GIT_COMMIT_SHORT}")
 endif()
 
-# Audacity-X.Y.Z-alpha-20210615
-set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
+# Custom variables use CPACK_AUDACITY_ prefix. CPACK_ to expose to CPack,
+# AUDACITY_ to show it is custom and avoid conflicts with other projects.
+set(CPACK_AUDACITY_SOURCE_DIR "${PROJECT_SOURCE_DIR}")
 
-if(NOT "$ENV{AUDACITY_ARCH_LABEL}" STREQUAL "")
-   # Audacity-X.Y.Z-alpha-20210615-x86_64
-   set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-$ENV{AUDACITY_ARCH_LABEL}")
+if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+   set(os "win")
+elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
+   set(os "macos")
+elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
+   set(os "linux")
+endif()
+
+# audacity-linux-X.Y.Z-alpha-20210615
+set(CPACK_PACKAGE_FILE_NAME "audacity-${os}-${CPACK_PACKAGE_VERSION}")
+set(zsync_name "audacity-${os}-*") # '*' is wildcard (here it means any version)
+
+if(DEFINED AUDACITY_ARCH_LABEL)
+   # audacity-linux-X.Y.Z-alpha-20210615-x86_64
+   set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${AUDACITY_ARCH_LABEL}")
+   set(zsync_name "${zsync_name}-${AUDACITY_ARCH_LABEL}")
+   set(CPACK_AUDACITY_ARCH_LABEL "${AUDACITY_ARCH_LABEL}")
 endif()
 set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}/package")
 
-set(CPACK_GENERATOR ZIP)
+set(CPACK_GENERATOR "ZIP")
 
-if( CMAKE_SYSTEM_NAME STREQUAL "Darwin" )
+if(CMAKE_SYSTEM_NAME MATCHES "Linux")
+   set(CPACK_GENERATOR "External")
+   set(CPACK_EXTERNAL_ENABLE_STAGING TRUE)
+   set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${PROJECT_SOURCE_DIR}/linux/package_appimage.cmake")
+   if(AUDACITY_BUILD_LEVEL EQUAL 2)
+      # Enable updates. See https://github.com/AppImage/AppImageSpec/blob/master/draft.md#update-information
+      set(CPACK_AUDACITY_APPIMAGE_UPDATE_INFO "gh-releases-zsync|audacity|audacity|latest|${zsync_name}.AppImage.zsync")
+   endif()
+elseif( CMAKE_SYSTEM_NAME STREQUAL "Darwin" )
    set( CPACK_GENERATOR DragNDrop )
 
    set( CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/mac/Resources/Audacity-DMG-background.png")
    set( CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_SOURCE_DIR}/scripts/build/macOS/DMGSetup.scpt")
-   
+
    if( ${_OPT}perform_codesign )
       set( CPACK_APPLE_CODESIGN_IDENTITY ${APPLE_CODESIGN_IDENTITY} )
       set( CPACK_APPLE_NOTARIZATION_USER_NAME ${APPLE_NOTARIZATION_USER_NAME} )
       set( CPACK_APPLE_NOTARIZATION_PASSWORD ${APPLE_NOTARIZATION_PASSWORD} )
       set( CPACK_APPLE_SIGN_SCRIPTS "${CMAKE_SOURCE_DIR}/scripts/build/macOS" )
       set( CPACK_PERFORM_NOTARIZATION ${${_OPT}perform_notarization} )
-      
+
       # CPACK_POST_BUILD_SCRIPTS was added in 3.19, but we only need it on macOS
       SET( CPACK_POST_BUILD_SCRIPTS "${CMAKE_SOURCE_DIR}/scripts/build/macOS/DMGSign.cmake" )
    endif()
diff --git a/help/CMakeLists.txt b/help/CMakeLists.txt
index 30e7395ea..ac4a644e1 100755
--- a/help/CMakeLists.txt
+++ b/help/CMakeLists.txt
@@ -55,6 +55,6 @@ if( NOT CMAKE_SYSTEM_NAME MATCHES "Darwin" )
       install( FILES "${_SRCDIR}/audacity.1"
                DESTINATION "${_MANDIR}/man1" )
       install( FILES "${_SRCDIR}/audacity.appdata.xml"
-               DESTINATION "${_DATADIR}/appdata" )
+               DESTINATION "${_DATADIR}/metainfo" )
    endif()
 endif()
diff --git a/help/audacity.appdata.xml b/help/audacity.appdata.xml
index 3249d5283..5dc3daf99 100644
--- a/help/audacity.appdata.xml
+++ b/help/audacity.appdata.xml
@@ -27,7 +27,6 @@
   <url type="bugtracker">https://bugzilla.audacityteam.org/</url>
   <url type="faq">https://manual.audacityteam.org/man/faq.html</url>
   <url type="help">https://manual.audacityteam.org/</url>
-  <url type="donation">https://www.audacityteam.org/donate/</url>
   <url type="translate">https://www.audacityteam.org/community/translators/</url>
   <screenshots>
     <screenshot type="default">
diff --git a/linux/AppRun.sh b/linux/AppRun.sh
new file mode 100755
index 000000000..623a52b6c
--- /dev/null
+++ b/linux/AppRun.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# The AppImage runtime sets some special environment variables. We provide
+# default values here in case the user tries to run this script outside an
+# AppImage where the variables would otherwise be undefined.
+if [[ ! "${APPIMAGE}" || ! "${APPDIR}" ]]; then
+    export APPIMAGE="$(readlink -f "${0}")"
+    export APPDIR="$(dirname "${APPIMAGE}")"
+fi
+
+export LD_LIBRARY_PATH="${APPDIR}/lib:${LD_LIBRARY_PATH}"
+
+function help()
+{
+    # Normal audacity help
+    "${APPDIR}/bin/audacity" --help
+    # Special options handled by this script
+    cat >&2 <<EOF
+  --readme              display README
+  --license             display LICENSE
+  --man[ual|page]       display audacity(1) manual page
+  --check-dependencies  check library dependency fulfillment (developer tool)
+
+EOF
+    # Blank line then special options handled by the AppImage runtime
+    "${APPIMAGE}" --appimage-help
+}
+
+# Intercept command line arguments
+case "$1" in
+-h|--help )
+    help
+    ;;
+--readme )
+    exec less "${APPDIR}/share/doc/audacity/README.txt"
+    ;;
+--license )
+    exec less "${APPDIR}/share/doc/audacity/LICENSE.txt"
+    ;;
+--man|--manual|--manpage )
+    exec man "${APPDIR}/share/man/man1/audacity.1"
+    ;;
+--check-depends|--check-dependencies )
+    exec bash "${APPDIR}/bin/check_dependencies"
+    ;;
+* )
+    # Other arguments go to Audacity
+    exec "${APPDIR}/bin/audacity" "$@"
+    ;;
+esac
diff --git a/linux/check_dependencies.sh b/linux/check_dependencies.sh
new file mode 100755
index 000000000..e93fa76ec
--- /dev/null
+++ b/linux/check_dependencies.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+
+version="@CPACK_PACKAGE_NAME@ v@CPACK_PACKAGE_VERSION@ @CPACK_AUDACITY_ARCH_LABEL@"
+
+export LC_ALL=C # Using `sort` a lot. Order depends on locale so override it.
+
+# PROBLEM: We want to check dependencies provided by the system, but `ldd`
+# looks in the current directory first so will show other package libraries.
+# SOLUTION: Copy library to temporary folder and test it there.
+function check_file()
+{
+    counter=$(($(cat "$2/.counter")+1))
+    echo ${counter} > "$2/.counter"
+    printf >&2 "Done ${counter} of $3.\r"
+    cp "$1" "$2"
+    file="$(basename "$1")"
+    LANG=C LD_LIBRARY_PATH="" "${APPDIR}/bin/ldd_recursive" -uniq "$2/${file}" 2>/dev/null
+    rm "$2/${file}" # delete library before we test the next one
+}
+export -f check_file # make function available in Bash subprocesses
+
+function prep_result()
+{
+    sed -n "s|$2||p" "$1" | sort -f | tee "$3" | wc -l
+}
+
+if ! which less >/dev/null; then
+    function less() { cat "$@"; } # use `cat` if `less` is unavailable
+fi
+
+tmp="$(mktemp -d)"
+trap "rm -rf '${tmp}'" EXIT
+
+cd "${APPDIR}"
+
+find . -executable -type f \! -name "lib*.so*" > "${tmp}/exes.txt"
+find . -name "lib*.so*"    \! -type l          > "${tmp}/libs.txt"
+
+num_exes="$(<"${tmp}/exes.txt" xargs -n1 basename 2>/dev/null | tee "${tmp}/exes2.txt" | wc -l)"
+num_libs="$(<"${tmp}/libs.txt" xargs -n1 basename 2>/dev/null | tee "${tmp}/libs2.txt" | wc -l)"
+
+echo >&2 "AppImage contains ${num_exes} executables and ${num_libs} libraries."
+
+echo >&2 "Checking dependencies for executables and libraries..."
+include_libs="${tmp}/libs.txt"
+num_includes="$((${num_libs}+${num_exes}))"
+
+# Check dependencies against system. See 'check_file' function.
+echo 0 > "${tmp}/.counter"
+cat "${tmp}/exes.txt" "${include_libs}" | xargs -n1 -I '%%%' bash -c \
+'check_file "${0}" "${1}" "${2}"' "%%%" "${tmp}" "${num_includes}" \; \
+| sort | uniq > "${tmp}/deps.txt"
+echo >&2 "Processing results."
+
+mv "${tmp}/libs2.txt" "${tmp}/libs.txt"
+mv "${tmp}/exes2.txt" "${tmp}/exes.txt"
+
+# Have only checked system libraries. Now consider those in package:
+<"${tmp}/libs.txt" xargs -n1 -I '%%%' sed -i 's|^%%% => not found$|%%% => package|' "${tmp}/deps.txt"
+<"${tmp}/libs.txt" xargs -n1 -I '%%%' sed -i 's|%%%$|%%% => both|' "${tmp}/deps.txt"
+
+# Remaining dependencies must be system:
+sed -ri 's/^(.*[^(not found|package|both)])$/\1 => system/' "${tmp}/deps.txt"
+
+num_package=$(prep_result "${tmp}/deps.txt" ' => package$'   "${tmp}/package.txt")
+num_system=$(prep_result  "${tmp}/deps.txt" ' => system$'    "${tmp}/system.txt")
+num_both=$(prep_result    "${tmp}/deps.txt" ' => both$'      "${tmp}/both.txt")
+num_neither=$(prep_result "${tmp}/deps.txt" ' => not found$' "${tmp}/neither.txt")
+
+# Any libraries included in package that don't appear in 'deps.txt' **might**
+# not actually be needed. Careful: they might be needed by a plugin!
+num_extra=$(<"${tmp}/libs.txt" xargs -n1 -I '%%%' sh -c \
+    "grep -q '%%%' \"${tmp}/deps.txt\" || echo '%%%'" \
+    | sort -f | tee "${tmp}/extra.txt" | wc -l)
+
+less <<EOF
+# Package: ${version}
+# System: $(uname -srmo)
+$(cat /etc/*release*)
+
+# In package only: ${num_package}
+$(cat "${tmp}/package.txt")
+
+# System only: ${num_system}
+$(cat "${tmp}/system.txt")
+
+# Provided by both: ${num_both}
+$(cat "${tmp}/both.txt")
+
+# Provided by neither: ${num_neither}
+$(cat "${tmp}/neither.txt")
+
+# Extra: ${num_extra} (In package but unlinked. Possibly needed by plugins.)
+$(cat "${tmp}/extra.txt")
+EOF
diff --git a/linux/create_appimage.sh b/linux/create_appimage.sh
new file mode 100755
index 000000000..0941dd88d
--- /dev/null
+++ b/linux/create_appimage.sh
@@ -0,0 +1,113 @@
+#!/usr/bin/env bash
+
+((${BASH_VERSION%%.*} >= 4)) || { echo >&2 "$0: Error: Please upgrade Bash."; exit 1; }
+
+set -euxo pipefail
+
+readonly appdir="$1" # input path (Audacity install directory)
+readonly appimage="$2" # output path to use for created AppImage
+
+#============================================================================
+# Helper functions
+#============================================================================
+
+function download_github_release()
+{
+    local -r repo_slug="$1" release_tag="$2" file="$3"
+    wget -q --show-progress "https://github.com/${repo_slug}/releases/download/${release_tag}/${file}"
+    chmod +x "${file}"
+}
+
+function extract_appimage()
+{
+    # Extract AppImage so we can run it without having to install FUSE
+    local -r image="$1" binary_name="$2"
+    local -r dir="${image%.AppImage}.AppDir"
+    "./${image}" --appimage-extract >/dev/null # dest folder "squashfs-root"
+    mv squashfs-root "${dir}" # rename folder to avoid collisions
+    ln -s "${dir}/AppRun" "${binary_name}" # symlink for convenience
+    rm -f "${image}"
+}
+
+function download_appimage_release()
+{
+    local -r github_repo_slug="$1" binary_name="$2" tag="$3"
+    local -r image="${binary_name}-x86_64.AppImage"
+    download_github_release "${github_repo_slug}" "${tag}" "${image}"
+    extract_appimage "${image}" "${binary_name}"
+}
+
+function download_linuxdeploy_component()
+{
+    local -r component="$1" tag="$2"
+    download_appimage_release "linuxdeploy/$1" "$1" "$2"
+}
+
+function create_path()
+{
+    local -r path="$1"
+    if [[ -d "${path}" ]]; then
+        return 1 # already exists
+    fi
+    mkdir -p "${path}"
+}
+
+#============================================================================
+# Fetch AppImage packaging tools
+#============================================================================
+
+if create_path "appimagetool"; then
+(
+    cd "appimagetool"
+    download_appimage_release AppImage/AppImageKit appimagetool continuous
+)
+fi
+export PATH="${PWD%/}/appimagetool:${PATH}"
+appimagetool --version
+
+if create_path "linuxdeploy"; then
+(
+    cd "linuxdeploy"
+    download_linuxdeploy_component linuxdeploy continuous
+)
+fi
+export PATH="${PWD%/}/linuxdeploy:${PATH}"
+linuxdeploy --list-plugins
+
+#============================================================================
+# Create symlinks
+#============================================================================
+
+ln -sf --no-dereference . "${appdir}/usr"
+ln -sf share/applications/audacity.desktop "${appdir}/audacity.desktop"
+ln -sf share/icons/hicolor/scalable/apps/audacity.svg "${appdir}/audacity.svg"
+ln -sf share/icons/hicolor/scalable/apps/audacity.svg "${appdir}/.DirIcon"
+
+#============================================================================
+# Bundle dependencies
+#============================================================================
+
+# HACK: Some wxWidget libraries depend on themselves. Add
+# them to LD_LIBRARY_PATH so that linuxdeploy can find them.
+export LD_LIBRARY_PATH="${appdir}/usr/lib/audacity:${LD_LIBRARY_PATH-}"
+
+linuxdeploy --appdir "${appdir}" # add all shared library dependencies
+
+#============================================================================
+# Build AppImage
+#============================================================================
+
+appimagetool_args=(
+    # none
+)
+
+if [[ "${AUDACITY_UPDATE_INFO-}" ]]; then
+    # Enable updates. See https://github.com/AppImage/AppImageSpec/blob/master/draft.md#update-information
+    appimagetool_args+=( --updateinformation="${AUDACITY_UPDATE_INFO}" )
+else
+    echo >&2 "$0: Automatic updates disabled"
+fi
+
+# Create AppImage
+cd "$(dirname "${appimage}")" # otherwise zsync created in wrong directory
+appimagetool "${appimagetool_args[@]}" "${appdir}" "${appimage}"
diff --git a/linux/ldd_recursive.pl b/linux/ldd_recursive.pl
new file mode 100755
index 000000000..fde7c519f
--- /dev/null
+++ b/linux/ldd_recursive.pl
@@ -0,0 +1,205 @@
+#!/usr/bin/env perl
+
+###############################################################################
+#
+# Written by Igor Ljubuncic (igor.ljubuncic@intel.com)
+#            Yuval Nissan (yuval.nissan@intel.com)
+#
+# Version 1.0 on Mar 29, 2011
+#
+# This program performs recursive ldd checks for binaries and libraries
+# It recurses through entire ldd tree for every listed binary and library
+# It completes when no matches found in the current branch
+# Same limitations to standard ldd apply
+# ldd cannot check libraries with no permissions
+#
+###############################################################################
+
+# /*
+#
+#   This file is provided under a dual BSD/GPLv2 license.  When using or
+#   redistributing this file, you may do so under either license.
+#
+#   GPL LICENSE SUMMARY
+#
+#   Copyright(c) 2011 Intel Corporation. All rights reserved.
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of version 2 of the GNU General Public License as
+#   published by the Free Software Foundation.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#   General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+#   The full GNU General Public License is included in this distribution
+#   in the file called LICENSE.GPL.
+#
+#   Contact Information:
+#   Igor Ljubuncic, igor.ljubuncic@intel.com
+#   P.O.B 1659, MATAM, 31015 Haifa, Israel
+#
+#   BSD LICENSE
+#
+#   Copyright(c) 2011 Intel Corporation. All rights reserved.
+#   All rights reserved.
+#
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions
+#   are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the
+#       distribution.
+#     * Neither the name of Intel Corporation nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+#   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+#  */
+
+
+use strict;
+use Data::Dumper;
+use Getopt::Long;
+
+# global variables
+# ----------------
+
+my $result;
+my @inputs = ();
+my $helpoption=0;
+my $debug=0;
+my $verbose=0;
+my $sep;
+my $print_vars;
+my $uniq;
+my %uniq;
+
+#############################################
+#############################################
+###                                       ###
+###             FUNCTION MAIN             ###
+###                                       ###
+#############################################
+#############################################
+
+
+GetOptions(
+    'h|help+'   => \$helpoption,
+    'debug+'    => \$verbose,
+    't=s'       => \$sep,
+    'l'         => \$print_vars,
+    'uniq'      => \$uniq
+);
+
+if ($verbose==1) {
+    # Print messages for debug purposes
+    $debug = 1;
+}
+
+if (($helpoption==1) || ($ARGV[0] eq "")) {
+    print "\nRecursive ldd v1.00\n";
+    print "Written by Igor Ljubuncic (igor.ljubuncic\@intel.com)\n";
+    print "Written by Yuval Nissan (yuval.nissan\@intel.com)\n\n";
+    print "Usage mode:\n\n";
+    print "-d\t\tverbose output\n";
+    print "-h|help\t\tprint help\n";
+    print "-t\t\tdelimiter\n";
+    print "-l\t\tprint env. variables\n";
+    print "-uniq\t\tprint unique values only\n\n";
+    exit 0;
+}
+
+$sep ||= "\t";
+
+push @inputs, @ARGV;
+
+if($print_vars) {
+    print "ldd output can be affected by:\n";
+    print "\$LD_LIBRARY_PATH = '".$ENV{LD_LIBRARY_PATH}."'\n";
+    print "\$LD_PRELOAD = '".$ENV{LD_PRELOAD}."'\n\n";
+}
+
+&recurseLibs($inputs[0], 0);
+delete $uniq{$inputs[0]};
+print join("\n", keys(%uniq))."\n" if $uniq;
+
+exit 0;
+
+##############################
+##############################
+##                          ##
+##        FUNCTIONS         ##
+##                          ##
+##############################
+##############################
+
+sub recurseLibs
+{
+    my $filename=shift;
+    my $depth = shift;
+    print "Working on file: $filename\n" if $debug;
+    print "$sep"x$depth if not $uniq;
+    ++$depth;
+    return if $uniq{$filename} and $uniq;
+    $uniq{$filename} = 1;
+    print "$filename\n" if not $uniq;
+    chomp(my @libraries = `/usr/bin/ldd $filename`);
+        print "Libraries:\n@libraries\n" if $debug;
+
+        foreach my $line (@libraries) {
+        next if not $line;
+        $line =~ s/^\s+//g;
+        $line =~ s/\s+$//g;
+        # If static or else
+        if (($line =~ /statically linked/) or ($line =~ /not a dynamic executable/)) {
+                return;
+        }
+        elsif($line =~ /not found/) {
+            print "$sep"x$depth if not $uniq;
+            print "$line\n" if not $uniq;
+            $uniq{$line} = 1;
+            next;
+        }
+
+
+        # Split and recurse on libraries (third value is the lib path):
+        my @newlibs = split(/\s+/,$line);
+        print Dumper(\@newlibs) if $debug;
+
+        # Skip if no mapped or directly linked
+        # Sane output comes with four elements
+        if (scalar(@newlibs) < 4) {
+            print "$sep"x$depth if not $uniq;
+            print $newlibs[0]."\n" if not $uniq;
+            $uniq{$newlibs[0]} = 1;
+            next;
+        }
+
+        print "\nI'm gonna enter recursion with $newlibs[2].\n\n" if $debug;
+        &recurseLibs($newlibs[2], $depth);
+
+    }
+    return;
+}
+
+__END__
diff --git a/linux/package_appimage.cmake b/linux/package_appimage.cmake
new file mode 100644
index 000000000..0c334e6ba
--- /dev/null
+++ b/linux/package_appimage.cmake
@@ -0,0 +1,27 @@
+if(CPACK_EXTERNAL_ENABLE_STAGING)
+   set(appdir "${CPACK_TEMPORARY_DIRECTORY}")
+else()
+   set(appdir "${CPACK_INSTALL_PREFIX}")
+endif()
+set(appimage "${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}.AppImage")
+
+set(CPACK_EXTERNAL_BUILT_PACKAGES "${appimage}")
+
+if(DEFINED CPACK_AUDACITY_APPIMAGE_UPDATE_INFO)
+   set(ENV{AUDACITY_UPDATE_INFO} "${CPACK_AUDACITY_APPIMAGE_UPDATE_INFO}")
+   list(APPEND CPACK_EXTERNAL_BUILT_PACKAGES "${appimage}.zsync")
+endif()
+
+configure_file("${CPACK_AUDACITY_SOURCE_DIR}/linux/AppRun.sh" "${appdir}/AppRun" ESCAPE_QUOTES @ONLY)
+configure_file("${CPACK_AUDACITY_SOURCE_DIR}/linux/check_dependencies.sh" "${appdir}/bin/check_dependencies" ESCAPE_QUOTES @ONLY)
+configure_file("${CPACK_AUDACITY_SOURCE_DIR}/linux/ldd_recursive.pl" "${appdir}/bin/ldd_recursive" COPYONLY)
+
+execute_process(
+   COMMAND "linux/create_appimage.sh" "${appdir}" "${appimage}"
+   WORKING_DIRECTORY "${CPACK_AUDACITY_SOURCE_DIR}"
+   RESULT_VARIABLE exit_status
+)
+
+if(NOT "${exit_status}" EQUAL "0")
+   message(FATAL_ERROR "Could not create AppImage. See output above for details.")
+endif()
diff --git a/scripts/ci/dependencies.sh b/scripts/ci/dependencies.sh
index b96eb40eb..f91eef81c 100755
--- a/scripts/ci/dependencies.sh
+++ b/scripts/ci/dependencies.sh
@@ -23,9 +23,20 @@ elif [[ "${OSTYPE}" == darwin* ]]; then # macOS
 
 else # Linux & others
 
+    if ! which sudo; then
+        function sudo() { "$@"; } # no-op sudo for use in Docker images
+    fi
+
     # Distribution packages
     if which apt-get; then
         apt_packages=(
+            # Docker image
+            file
+            g++
+            git
+            wget
+
+            # GitHub Actions
             libasound2-dev
             libgtk2.0-dev
             gettext
@@ -43,6 +54,9 @@ else # Linux & others
     pip_packages=(
         conan
     )
+
+    which cmake || pip_packages+=( cmake ) # get latest CMake when inside Docker image
+
     pip3 install wheel setuptools # need these first to install other packages (e.g. conan)
     pip3 install "${pip_packages[@]}"
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b749b80f4..eea73d7d5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1243,6 +1243,9 @@ else()
    #
    # (Don't use generator expressions since it will leave null/empty
    # entries in the list.)
+   set( MIMETYPES
+      application/x-audacity-project
+   )
    if( USE_FFMPEG )
       list( APPEND MIMETYPES
          audio/aac
diff --git a/src/audacity.desktop.in b/src/audacity.desktop.in
index 275fa9c0b..5feb41277 100644
--- a/src/audacity.desktop.in
+++ b/src/audacity.desktop.in
@@ -53,4 +53,4 @@ Categories=AudioVideo;Audio;AudioVideoEditing;
 Exec=env UBUNTU_MENUPROXY=0 @AUDACITY_NAME@ %F
 StartupNotify=false
 Terminal=false
-MimeType=application/x-audacity-project;@MIMETYPES@
+MimeType=@MIMETYPES@;