diff --git a/admin/RunExport b/admin/RunExport
index 4488065158..e17a472cdf 100755
--- a/admin/RunExport
+++ b/admin/RunExport
@@ -17,7 +17,6 @@ cd "$MB_SERVER_ROOT"
 source admin/config.sh
 
 . ./admin/functions.sh
-make_temp_dir
 
 set +o errexit
 WITH_FULL_EXPORT=$1 ; shift
@@ -51,6 +50,16 @@ chown "$FTP_USER:$FTP_GROUP" \
     "$FTP_DATA_DIR"/fullexport/"$EXPORT_STAMP" \
     "$FTP_DATA_DIR"/replication
 
+# Create temporary directory under the same volume as the final
+# destination so that moving files does not require copying those.
+# If we're not doing a full export, just use default tmp instead.
+if [ "$WITH_FULL_EXPORT" ]
+then
+    make_temp_dir "$FTP_DATA_DIR"/fullexport/
+else
+    make_temp_dir
+fi
+
 echo Making database snapshot
 ./admin/ExportAllTables \
     $EXPORT_ARG \
diff --git a/admin/RunIncrementalJSONDump b/admin/RunIncrementalJSONDump
index 697f02e6e6..70dd04fffd 100755
--- a/admin/RunIncrementalJSONDump
+++ b/admin/RunIncrementalJSONDump
@@ -21,7 +21,6 @@ cd "$MB_SERVER_ROOT"
 source admin/config.sh
 
 . ./admin/functions.sh
-make_temp_dir
 
 DUMP_DIR="$JSON_DUMP_DIR"/incremental
 
@@ -31,6 +30,11 @@ DUMP_DIR="$JSON_DUMP_DIR"/incremental
 mkdir -m "$JSON_DUMP_DIR_MODE" -p "$DUMP_DIR"
 chown "$JSON_DUMP_USER:$JSON_DUMP_GROUP" "$DUMP_DIR"
 
+# Create temporary directory under the same volume as the final
+# destination so that moving files does not require copying those.
+
+make_temp_dir "$DUMP_DIR"
+
 # The database from which the dumps are generated is replicated, but created
 # as standalone - meaning it has foreign keys and triggers. Foreign keys are
 # needed for MusicBrainz::Server::Role::FollowForeignKeys to work. The
@@ -43,6 +47,7 @@ echo Disabling all database triggers
 echo Making incremental JSON dumps
 ./admin/DumpIncrementalJSON \
     --output-dir "$TEMP_DIR" \
+    --tmp-dir "$(make_temp_dir --extra "$DUMP_DIR")" \
     --compress \
     --database READWRITE \
     || exit $?
diff --git a/admin/RunJSONDump b/admin/RunJSONDump
index c42903b757..0b44ec9155 100755
--- a/admin/RunJSONDump
+++ b/admin/RunJSONDump
@@ -21,7 +21,6 @@ cd "$MB_SERVER_ROOT"
 source admin/config.sh
 
 . ./admin/functions.sh
-make_temp_dir
 
 DUMP_DIR="$JSON_DUMP_DIR/full"
 DUMP_STAMP=`TZ=UTC date +'%Y%m%d-%H%M%S'`
@@ -35,9 +34,15 @@ chown "$JSON_DUMP_USER:$JSON_DUMP_GROUP" \
     "$DUMP_DIR" \
     "$DUMP_DIR"/"$DUMP_STAMP"
 
+# Create temporary directory under the same volume as the final
+# destination so that moving files does not require copying those.
+
+make_temp_dir "$DUMP_DIR"
+
 echo Making a full JSON dump
 ./admin/DumpJSON \
     --output-dir "$TEMP_DIR" \
+    --tmp-dir "$(make_temp_dir --extra "$DUMP_DIR")" \
     --compress \
     --database READWRITE \
     --worker-count 4 \
diff --git a/admin/RunSampleDataDump b/admin/RunSampleDataDump
index e8ac49965a..f2bfa68421 100755
--- a/admin/RunSampleDataDump
+++ b/admin/RunSampleDataDump
@@ -17,7 +17,6 @@ cd "$MB_SERVER_ROOT"
 source admin/config.sh
 
 . ./admin/functions.sh
-make_temp_dir
 
 DUMP_DIR="$FTP_DATA_DIR/sample"
 DUMP_STAMP=`TZ=UTC date +'%Y%m%d-%H%M%S'`
@@ -29,6 +28,11 @@ chown "$FTP_USER:$FTP_GROUP" \
     "$DUMP_DIR" \
     "$DUMP_DIR/$DUMP_STAMP"
 
+# Create temporary directory under the same volume as the final
+# destination so that moving files does not require copying those.
+
+make_temp_dir "$DUMP_DIR"
+
 echo Making a sample database dump
 ./admin/DumpSampleData \
     --output-dir "$TEMP_DIR" \
diff --git a/admin/RunSearchIndexesDump b/admin/RunSearchIndexesDump
index 442e79878d..2a5db7c38b 100755
--- a/admin/RunSearchIndexesDump
+++ b/admin/RunSearchIndexesDump
@@ -21,7 +21,6 @@ cd "$MB_SERVER_ROOT"
 source admin/config.sh
 
 . ./admin/functions.sh
-make_temp_dir
 
 DUMP_DIR="$SEARCH_INDEXES_DUMP_DIR"
 DUMP_STAMP=`TZ=UTC date +'%Y%m%d-%H%M%S'`
@@ -32,6 +31,11 @@ chown "$SEARCH_INDEXES_DUMP_USER:$SEARCH_INDEXES_DUMP_GROUP" \
     "$DUMP_DIR" \
     "$DUMP_DIR"/"$DUMP_STAMP"
 
+# Create temporary directory under the same volume as the final
+# destination so that moving files does not require copying those.
+
+make_temp_dir "$DUMP_DIR"
+
 echo Requesting SolrCloud backups
 BACKUP_STAMP="$DUMP_STAMP" \
     ./admin/RequestSolrCloudBackups \
diff --git a/admin/functions.sh b/admin/functions.sh
index 5009a2eb0f..51e0f97841 100644
--- a/admin/functions.sh
+++ b/admin/functions.sh
@@ -2,8 +2,14 @@
 
 make_temp_dir()
 {
-    # Make a temporary directory dedicated to this invocation
-    TEMP_DIR=`
+    local is_extra=no
+    if [[ $# -gt 0 ]] && [[ $1 = '--extra' ]]
+    then
+      is_extra=yes
+      shift
+    fi
+    # Make a temporary directory
+    local temp_dir=`
         perl -MFile::Temp=tempdir -e'
                 $dir = tempdir(
                         "mbscript-XXXXXXXX",
@@ -12,8 +18,16 @@ make_temp_dir()
                         TMPDIR => 1,
                 ) or die $!;
                 print $dir;
-        '
+        ' "$@"
     ` || exit $?
+    # If it's an extra directory, just return its full path
+    if [[ $is_extra = yes ]]
+    then
+      echo "$temp_dir"
+      return
+    fi
+    # Otherwise dedicate this directory to this invocation
+    TEMP_DIR="$temp_dir"
     echo "Using temporary directory $TEMP_DIR"
     # Note that this only removes the directory if it's empty.  This is a
     # trade-off; we opt to not lose any files accidentally, at the risk of perhaps
diff --git a/bin/rsync-fullexport-files b/bin/rsync-fullexport-files
index e855231bef..7f1f652961 100755
--- a/bin/rsync-fullexport-files
+++ b/bin/rsync-fullexport-files
@@ -15,6 +15,7 @@ if [ -n "$RSYNC_FULLEXPORT_HOST" ]; then
         --archive \
         --delete \
         --exclude='latest-is*' \
+        --exclude='mbscript-*' \
         --rsh "ssh -i $RSYNC_FULLEXPORT_KEY -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p $RSYNC_FULLEXPORT_PORT" \
         --verbose \
         $RSYNC_FULLEXPORT_DIR/ \
diff --git a/lib/MusicBrainz/Script/JSONDump.pm b/lib/MusicBrainz/Script/JSONDump.pm
index 726df8ebdd..7818133238 100644
--- a/lib/MusicBrainz/Script/JSONDump.pm
+++ b/lib/MusicBrainz/Script/JSONDump.pm
@@ -8,7 +8,6 @@ use aliased 'MusicBrainz::Server::DatabaseConnectionFactory';
 use Data::Dumper;
 use DBDefs;
 use File::Copy qw( move );
-use File::Path qw( rmtree );
 use File::Spec::Functions qw( catdir catfile tmpdir );
 use File::Temp qw( tempdir );
 use Fcntl qw( :flock );
@@ -69,21 +68,31 @@ has output_dir => (
     documentation => 'location where dumps are outputted (default: .)',
 );
 
-with 'MusicBrainz::Script::Role::TestCacheNamespace';
-
-our $TMP_EXPORT_DIR = tempdir(
-    'json-dump-XXXXXX',
-    DIR => tmpdir(),
-    CLEANUP => 0,
+has tmp_export_dir => (
+    is => 'ro',
+    isa => 'Str',
+    lazy => 1,
+    default => sub {
+      tempdir(
+        'json-dump-XXXXXX',
+        DIR => tmpdir(),
+        CLEANUP => 0,
+      )
+    },
+    traits => ['Getopt'],
+    cmd_flag => 'tmp-dir',
+    documentation => 'location where dumps are temporarily created (default: /tmp/json-dump-XXXXXX)',
 );
 
+with 'MusicBrainz::Script::Role::TestCacheNamespace';
+
 Readonly our $BATCH_SIZE => 100;
 
 sub create_json_dump {
     my ($self, $c, $table_name, %mbdump_options) = @_;
 
     my $dump_fname = $ENTITIES{$table_name}{url};
-    my $dump_fpath = catfile($TMP_EXPORT_DIR, $dump_fname);
+    my $dump_fpath = catfile($self->tmp_export_dir, $dump_fname);
 
     return unless -s $dump_fpath;
 
@@ -121,6 +130,8 @@ EOF
              catdir($mbdump->output_dir, $dump_fname)) or die $!;
     }
 
+    unlink "$dump_fpath.lock" or die $!;
+
     return;
 }
 
@@ -423,7 +434,7 @@ sub run {
     my $exit_code = $self->run_impl($c);
 
     $c->connector->disconnect;
-    rmtree($TMP_EXPORT_DIR);
+    rmdir($self->tmp_export_dir) or die $!;
 
     log_info { 'Done' };
     return $exit_code;
diff --git a/lib/MusicBrainz/Script/JSONDump/Full.pm b/lib/MusicBrainz/Script/JSONDump/Full.pm
index 7df585704e..8b4370868b 100644
--- a/lib/MusicBrainz/Script/JSONDump/Full.pm
+++ b/lib/MusicBrainz/Script/JSONDump/Full.pm
@@ -53,8 +53,6 @@ has pm => (
     traits => ['NoGetopt'],
 );
 
-our $TMP_EXPORT_DIR = $MusicBrainz::Script::JSONDump::TMP_EXPORT_DIR;
-
 our $WORKER_BATCH_SIZE = 10000;
 
 sub do_batch {
@@ -62,7 +60,7 @@ sub do_batch {
         $replication_sequence) = @_;
 
     my $dump_fpath = catfile(
-        $TMP_EXPORT_DIR, $ENTITIES{$entity_type}{url});
+        $self->tmp_export_dir, $ENTITIES{$entity_type}{url});
 
     my $entities_json_callback = sub {
         my ($entities_json) = @_;
diff --git a/lib/MusicBrainz/Script/JSONDump/Incremental.pm b/lib/MusicBrainz/Script/JSONDump/Incremental.pm
index 6a01c26add..dd0fc5e096 100644
--- a/lib/MusicBrainz/Script/JSONDump/Incremental.pm
+++ b/lib/MusicBrainz/Script/JSONDump/Incremental.pm
@@ -18,8 +18,6 @@ with 'MooseX::Runnable';
 
 with 'MusicBrainz::Script::Role::IncrementalDump';
 
-our $TMP_EXPORT_DIR = $MusicBrainz::Script::JSONDump::TMP_EXPORT_DIR;
-
 sub dump_schema { 'json_dump' }
 
 sub get_changed_documents {
@@ -60,7 +58,7 @@ SQL
             $total_changed += $changed;
             my @changed_documents = map { $entities_json->{$_} } @{$changed_ids};
             my $dump_path = catfile(
-                $TMP_EXPORT_DIR, $ENTITIES{$entity_type}{url});
+                $self->tmp_export_dir, $ENTITIES{$entity_type}{url});
             $self->write_json($dump_path, \@changed_documents);
         }
     };
