diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 81962433815e01f1897804005565959d3b48235c..d8fca1274aeb03abafe37cc8c5718a079f7782fa 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -358,6 +358,344 @@ Internationalization
 --------------------
 
 We're using https://github.com/Polyconseil/vue-gettext to manage i18n in the project.
+<<<<<<< HEAD
+When working on the front-end, any end-user string should be marked as a translatable string,
+with the proper context, as described below.
+
+Translations in HTML
+^^^^^^^^^^^^^^^^^^^^
+
+Translations in HTML use the ``<translate>`` tag::
+
+    <template>
+      <div>
+        <h1><translate translate-context="Content/Profile/Header">User profile</translate></h1>
+        <p>
+          <translate
+            translate-context="Content/Profile/Paragraph"
+            :translate-params="{username: 'alice'}">
+            You are logged in as %{ username }
+          </translate>
+        </p>
+         <p>
+          <translate
+            translate-context="Content/Profile/Paragraph"
+            translate-plural="You have %{ count } new messages, that's a lot!"
+            :translate-n="unreadMessagesCount"
+            :translate-params="{count: unreadMessagesCount}">
+            You have 1 new message
+          </translate>
+        </p>
+      </div>
+    </template>
+
+Anything between the `<translate>` and `</translate>` delimiters will be considered as a translatable string.
+You can use variables in the translated string via the ``:translate-params="{var: 'value'}"`` directive, and reference them like this:
+``val value is %{ value }``.
+
+For pluralization, you need to use ``translate-params`` in conjunction with ``translate-plural`` and ``translate-n``:
+
+- ``translate-params`` should contain the variable you're using for pluralization (which is usually shown to the user)
+- ``translate-n`` should match the same variable
+- The ``<translate>`` delimiters contain the non-pluralized version of your string
+- The ``translate-plural`` directive contains the pluralized version of your string
+
+
+Translations in javascript
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Translations in javascript work by calling the ``this.$*gettext`` functions::
+
+    export default {
+      computed: {
+        strings () {
+          let tracksCount = 42
+          let playButton = this.$pgettext('Sidebar/Player/Button/Verb, Short', 'Play')
+          let loginMessage = this.$pgettext('*/Login/Message', 'Welcome back %{ username }')
+          let addedMessage = this.$npgettext('*/Player/Message', 'One track was queued', '%{ count } tracks were queued', tracksCount)
+          console.log(this.$gettextInterpolate(addedMessage, {count: tracksCount}))
+          console.log(this.$gettextInterpolate(loginMessage, {username: 'alice'}))
+        }
+      }
+    }
+
+The first argument of the ``$pgettext`` and ``$npgettext`` functions is the string context.
+
+Contextualization
+^^^^^^^^^^^^^^^^^
+
+Translation contexts provided via the ``translate-context`` directive and the ``$pgettext`` and ``$npgettext`` are never shown to end users
+but visible by Funkwhale translators. They help translators where and how the strings are used,
+especially with short or ambiguous strings, like ``May``, which can refer a month or a verb.
+
+While we could in theory use free form context, like ``This string is inside a button, in the main page, and is a call to action``,
+Funkwhale use a hierarchical structure to write contexts and keep them short and consistents accross the app. The previous context,
+rewritten correctly would be: ``Content/Home/Button/Call to action``.
+
+This hierarchical structure is made of several parts:
+
+- The location part, which is required and refers to the big blocks found in Funkwhale UI where the translated string is displayed:
+    - ``Content``
+    - ``Footer``
+    - ``Head``
+    - ``Menu``
+    - ``Popup``
+    - ``Sidebar``
+    - ``*`` for strings that are not tied to a specific location
+
+- The feature part, which is required, and refers to the feature associated with the translated string:
+    - ``About``
+    - ``Admin``
+    - ``Album``
+    - ``Artist``
+    - ``Embed``
+    - ``Home``
+    - ``Login``
+    - ``Library``
+    - ``Moderation``
+    - ``Player``
+    - ``Playlist``
+    - ``Profile``
+    - ``Favorites``
+    - ``Notifications``
+    - ``Radio``
+    - ``Search``
+    - ``Settings``
+    - ``Signup``
+    - ``Track``
+    - ``Queue``
+    - ``*`` for strings that are not tied to a specific feature
+
+- The component part, which is required and refers to the type of element that contain the string:
+    - ``Button``
+    - ``Card``
+    - ``Checkbox``
+    - ``Dropdown``
+    - ``Error message``
+    - ``Form``
+    - ``Header``
+    - ``Help text``
+    - ``Hidden text``
+    - ``Icon``
+    - ``Input``
+    - ``Image``
+    - ``Label``
+    - ``Link``
+    - ``List item``
+    - ``Menu``
+    - ``Message``
+    - ``Paragraph``
+    - ``Placeholder``
+    - ``Tab``
+    - ``Table``
+    - ``Title``
+    - ``Tooltip``
+    - ``*`` for strings that are not tied to a specific component
+
+The detail part, which is optional and refers to the contents of the string itself, such as:
+    - ``Adjective``
+    - ``Call to action``
+    - ``Noun``
+    - ``Short``
+    - ``Unit``
+    - ``Verb``
+
+Here are a few examples of valid context hierarchies:
+
+- ``Sidebar/Player/Button``
+- ``Content/Home/Button/Call to action``
+- ``Footer/*/Help text``
+- ``*/*/*/Verb, Short``
+- ``Popup/Playlist/Button``
+- ``Content/Admin/Table.Label/Short, Noun (Value is a date)``
+
+It's possible to nest multiple component parts to reach a higher level of detail. The component parts are then separated by a dot:
+
+- ``Sidebar/Queue/Tab.Title``
+- ``Content/*/Button.Title``
+- ``Content/*/Table.Header``
+- ``Footer/*/List item.Link``
+- ``Content/*/Form.Help text``
+
+Collecting translatable strings
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want to ensure your translatable strings are correctly marked for translation,
+you can try to extract them.
+||||||| merged common ancestors
+When working on the front-end, any end-user string should be translated
+using either ``<translate>yourstring</translate>`` or ``$gettext('yourstring')``
+function.
+=======
+<<<<<<< HEAD
+When working on the front-end, any end-user string should be translated
+using either ``<translate>yourstring</translate>`` or ``$gettext('yourstring')``
+function.
+||||||| parent of 21fb39dd... Update docs/developers/index.rst, docs/developers/subsonic.rst files
+When working on the front-end, any end-user string should be marked as a translatable string,
+with the proper context, as described below.
+
+Translations in HTML
+^^^^^^^^^^^^^^^^^^^^
+
+Translations in HTML use the ``<translate>`` tag::
+
+    <template>
+      <div>
+        <h1><translate translate-context="Content/Profile/Header">User profile</translate></h1>
+        <p>
+          <translate
+            translate-context="Content/Profile/Paragraph"
+            :translate-params="{username: 'alice'}">
+            You are logged in as %{ username }
+          </translate>
+        </p>
+         <p>
+          <translate
+            translate-context="Content/Profile/Paragraph"
+            translate-plural="You have %{ count } new messages, that's a lot!"
+            :translate-n="unreadMessagesCount"
+            :translate-params="{count: unreadMessagesCount}">
+            You have 1 new message
+          </translate>
+        </p>
+      </div>
+    </template>
+
+Anything between the `<translate>` and `</translate>` delimiters will be considered as a translatable string.
+You can use variables in the translated string via the ``:translate-params="{var: 'value'}"`` directive, and reference them like this:
+``val value is %{ value }``.
+
+For pluralization, you need to use ``translate-params`` in conjunction with ``translate-plural`` and ``translate-n``:
+
+- ``translate-params`` should contain the variable you're using for pluralization (which is usually shown to the user)
+- ``translate-n`` should match the same variable
+- The ``<translate>`` delimiters contain the non-pluralized version of your string
+- The ``translate-plural`` directive contains the pluralized version of your string
+
+
+Translations in javascript
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Translations in javascript work by calling the ``this.$*gettext`` functions::
+
+    export default {
+      computed: {
+        strings () {
+          let tracksCount = 42
+          let playButton = this.$pgettext('Sidebar/Player/Button/Verb, Short', 'Play')
+          let loginMessage = this.$pgettext('*/Login/Message', 'Welcome back %{ username }')
+          let addedMessage = this.$npgettext('*/Player/Message', 'One track was queued', '%{ count } tracks were queued', tracksCount)
+          console.log(this.$gettextInterpolate(addedMessage, {count: tracksCount}))
+          console.log(this.$gettextInterpolate(loginMessage, {username: 'alice'}))
+        }
+      }
+    }
+
+The first argument of the ``$pgettext`` and ``$npgettext`` functions is the string context.
+
+Contextualization
+^^^^^^^^^^^^^^^^^
+
+Translation contexts provided via the ``translate-context`` directive and the ``$pgettext`` and ``$npgettext`` are never shown to end users
+but visible by Funkwhale translators. They help translators where and how the strings are used,
+especially with short or ambiguous strings, like ``May``, which can refer a month or a verb.
+
+While we could in theory use free form context, like ``This string is inside a button, in the main page, and is a call to action``,
+Funkwhale use a hierarchical structure to write contexts and keep them short and consistents accross the app. The previous context,
+rewritten correctly would be: ``Content/Home/Button/Call to action``.
+
+This hierarchical structure is made of several parts:
+
+- The location part, which is required and refers to the big blocks found in Funkwhale UI where the translated string is displayed:
+    - ``Content``
+    - ``Footer``
+    - ``Head``
+    - ``Menu``
+    - ``Popup``
+    - ``Sidebar``
+    - ``*`` for strings that are not tied to a specific location
+
+- The feature part, which is required, and refers to the feature associated with the translated string:
+    - ``About``
+    - ``Admin``
+    - ``Album``
+    - ``Artist``
+    - ``Embed``
+    - ``Home``
+    - ``Login``
+    - ``Library``
+    - ``Moderation``
+    - ``Player``
+    - ``Playlist``
+    - ``Profile``
+    - ``Favorites``
+    - ``Notifications``
+    - ``Radio``
+    - ``Search``
+    - ``Settings``
+    - ``Signup``
+    - ``Track``
+    - ``Queue``
+    - ``*`` for strings that are not tied to a specific feature
+
+- The component part, which is required and refers to the type of element that contain the string:
+    - ``Button``
+    - ``Card``
+    - ``Checkbox``
+    - ``Dropdown``
+    - ``Error message``
+    - ``Form``
+    - ``Header``
+    - ``Help text``
+    - ``Hidden text``
+    - ``Icon``
+    - ``Input``
+    - ``Image``
+    - ``Label``
+    - ``Link``
+    - ``List item``
+    - ``Menu``
+    - ``Message``
+    - ``Paragraph``
+    - ``Placeholder``
+    - ``Tab``
+    - ``Table``
+    - ``Title``
+    - ``Tooltip``
+    - ``*`` for strings that are not tied to a specific component
+
+The detail part, which is optional and refers to the contents of the string itself, such as:
+    - ``Adjective``
+    - ``Call to action``
+    - ``Noun``
+    - ``Short``
+    - ``Unit``
+    - ``Verb``
+
+Here are a few examples of valid context hierarchies:
+
+- ``Sidebar/Player/Button``
+- ``Content/Home/Button/Call to action``
+- ``Footer/*/Help text``
+- ``*/*/*/Verb, Short``
+- ``Popup/Playlist/Button``
+- ``Content/Admin/Table.Label/Short, Noun (Value is a date)``
+
+It's possible to nest multiple component parts to reach a higher level of detail. The component parts are then separated by a dot:
+
+- ``Sidebar/Queue/Tab.Title``
+- ``Content/*/Button.Title``
+- ``Content/*/Table.Header``
+- ``Footer/*/List item.Link``
+- ``Content/*/Form.Help text``
+
+Collecting translatable strings
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want to ensure your translatable strings are correctly marked for translation,
+you can try to extract them.
+
 When working on the front-end, any end-user string should be marked as a translatable string,
 with the proper context, as described below.
 
diff --git a/api/funkwhale_api/federation/admin.py b/api/funkwhale_api/federation/admin.py
index acb2e5b67b33830e57550d9484516d0071c8c9df..8c9bbe31c8062bda8fd9192b14170f5289620b13 100644
--- a/api/funkwhale_api/federation/admin.py
+++ b/api/funkwhale_api/federation/admin.py
@@ -33,8 +33,8 @@ class DomainAdmin(admin.ModelAdmin):
 @admin.register(models.Activity)
 class ActivityAdmin(admin.ModelAdmin):
     list_display = ["type", "fid", "url", "actor", "creation_date"]
-    search_fields = ["payload", "fid", "url", "actor__domain"]
-    list_filter = ["type", "actor__domain"]
+    search_fields = ["payload", "fid", "url", "actor__domain__name"]
+    list_filter = ["type", "actor__domain__name"]
     actions = [redeliver_activities]
     list_select_related = True
 
@@ -49,7 +49,7 @@ class ActorAdmin(admin.ModelAdmin):
         "creation_date",
         "last_fetch_date",
     ]
-    search_fields = ["fid", "domain", "preferred_username"]
+    search_fields = ["fid", "domain__name", "preferred_username"]
     list_filter = ["type"]
 
 
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 44043b16cf28b3a7adce65f83db091b6a6abc135..0c5ce549796f9a7f03d11a3d475096bdff598ffa 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -859,7 +859,7 @@ class TrackSerializer(MusicEntitySerializer):
         from_activity = self.context.get("activity")
         if from_activity:
             metadata["from_activity_id"] = from_activity.pk
-        track = music_tasks.get_track_from_import_metadata(metadata)
+        track = music_tasks.get_track_from_import_metadata(metadata, update_cover=True)
         return track
 
 
diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py
index c8fed58410d9abacd4f5872451a48238b8988840..946d7a411f9de1e1eb6089fd148974718e20ea65 100644
--- a/api/funkwhale_api/music/tasks.py
+++ b/api/funkwhale_api/music/tasks.py
@@ -26,7 +26,9 @@ from . import serializers
 logger = logging.getLogger(__name__)
 
 
-def update_album_cover(album, source=None, cover_data=None, replace=False):
+def update_album_cover(
+    album, source=None, cover_data=None, musicbrainz=True, replace=False
+):
     if album.cover and not replace:
         return
     if cover_data:
@@ -39,7 +41,7 @@ def update_album_cover(album, source=None, cover_data=None, replace=False):
         cover = get_cover_from_fs(path)
         if cover:
             return album.get_image(data=cover)
-    if album.mbid:
+    if musicbrainz and album.mbid:
         try:
             logger.info(
                 "[Album %s] Fetching cover from musicbrainz release %s",
@@ -179,8 +181,8 @@ def process_upload(upload):
     import_metadata = upload.import_metadata or {}
     old_status = upload.import_status
     audio_file = upload.get_audio_file()
+    additional_data = {}
     try:
-        additional_data = {}
         if not audio_file:
             # we can only rely on user proveded data
             final_metadata = import_metadata
@@ -241,6 +243,15 @@ def process_upload(upload):
             "bitrate",
         ]
     )
+
+    # update album cover, if needed
+    if not track.album.cover:
+        update_album_cover(
+            track.album,
+            source=final_metadata.get("upload_source"),
+            cover_data=final_metadata.get("cover_data"),
+        )
+
     broadcast = getter(
         import_metadata, "funkwhale", "config", "broadcast", default=True
     )
@@ -369,7 +380,18 @@ def sort_candidates(candidates, important_fields):
 
 
 @transaction.atomic
-def get_track_from_import_metadata(data):
+def get_track_from_import_metadata(data, update_cover=False):
+    track = _get_track(data)
+    if update_cover and track and not track.album.cover:
+        update_album_cover(
+            track.album,
+            source=data.get("upload_source"),
+            cover_data=data.get("cover_data"),
+        )
+    return track
+
+
+def _get_track(data):
     track_uuid = getter(data, "funkwhale", "track", "uuid")
 
     if track_uuid:
@@ -380,12 +402,6 @@ def get_track_from_import_metadata(data):
         except models.Track.DoesNotExist:
             raise UploadImportError(code="track_uuid_not_found")
 
-        if not track.album.cover:
-            update_album_cover(
-                track.album,
-                source=data.get("upload_source"),
-                cover_data=data.get("cover_data"),
-            )
         return track
 
     from_activity_id = data.get("from_activity_id", None)
@@ -479,10 +495,6 @@ def get_track_from_import_metadata(data):
     album = get_best_candidate_or_create(
         models.Album, query, defaults=defaults, sort_fields=["mbid", "fid"]
     )[0]
-    if not album.cover:
-        update_album_cover(
-            album, source=data.get("upload_source"), cover_data=data.get("cover_data")
-        )
 
     # get / create track
     track_title = data["title"]
diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py
index 023e40cac29a89168e497977543f5995dece73b5..a53ad464038316f5efbd5810bd8a995e33a744ec 100644
--- a/api/funkwhale_api/subsonic/serializers.py
+++ b/api/funkwhale_api/subsonic/serializers.py
@@ -70,6 +70,7 @@ def get_track_data(album, track, upload):
         "album": album.title,
         "artist": album.artist.name,
         "track": track.position or 1,
+        "discNumber": track.disc_number or 1,
         "contentType": upload.mimetype,
         "suffix": upload.extension or "",
         "duration": upload.duration or 0,
diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py
index af1a9557db70ffcb266d2b354ac4acebd38ddd4e..ac62c31b86c3ebc7d893ac9b79361ab59792d516 100644
--- a/api/tests/music/test_tasks.py
+++ b/api/tests/music/test_tasks.py
@@ -153,7 +153,7 @@ def test_can_create_track_from_file_metadata_federation(factories, mocker, r_moc
     r_mock.get(metadata["cover_data"]["url"], body=io.BytesIO(b"coucou"))
     mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata)
 
-    track = tasks.get_track_from_import_metadata(metadata)
+    track = tasks.get_track_from_import_metadata(metadata, update_cover=True)
 
     assert track.title == metadata["title"]
     assert track.fid == metadata["fid"]
@@ -183,7 +183,9 @@ def test_sort_candidates(factories):
 
 def test_upload_import(now, factories, temp_signal, mocker):
     outbox = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
-    track = factories["music.Track"]()
+    update_album_cover = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
+    get_picture = mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture")
+    track = factories["music.Track"](album__cover="")
     upload = factories["music.Upload"](
         track=None, import_metadata={"funkwhale": {"track": {"uuid": str(track.uuid)}}}
     )
@@ -196,6 +198,10 @@ def test_upload_import(now, factories, temp_signal, mocker):
     assert upload.track == track
     assert upload.import_status == "finished"
     assert upload.import_date == now
+    get_picture.assert_called_once_with("cover_front", "other")
+    update_album_cover.assert_called_once_with(
+        upload.track.album, cover_data=get_picture.return_value, source=upload.source
+    )
     handler.assert_called_once_with(
         upload=upload,
         old_status="pending",
diff --git a/api/tests/subsonic/test_serializers.py b/api/tests/subsonic/test_serializers.py
index 85cb65fa791349df787dc3e742afc88ade5442d9..2c468a4f027e20b01d975552e8a841464e2e6d4a 100644
--- a/api/tests/subsonic/test_serializers.py
+++ b/api/tests/subsonic/test_serializers.py
@@ -64,7 +64,7 @@ def test_get_artist_serializer(factories):
 def test_get_album_serializer(factories):
     artist = factories["music.Artist"]()
     album = factories["music.Album"](artist=artist)
-    track = factories["music.Track"](album=album)
+    track = factories["music.Track"](album=album, disc_number=42)
     upload = factories["music.Upload"](track=track, bitrate=42000, duration=43, size=44)
 
     expected = {
@@ -85,6 +85,7 @@ def test_get_album_serializer(factories):
                 "album": album.title,
                 "artist": artist.name,
                 "track": track.position,
+                "discNumber": track.disc_number,
                 "year": track.album.release_date.year,
                 "contentType": upload.mimetype,
                 "suffix": upload.extension or "",
diff --git a/changes/changelog.d/757.bugfix b/changes/changelog.d/757.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..f9e9eae351a63297a17b790e957ad7dabba292bf
--- /dev/null
+++ b/changes/changelog.d/757.bugfix
@@ -0,0 +1 @@
+Ensure cover art from uploaded files is picked up properly on existing albums (#757)
diff --git a/changes/changelog.d/764.bugfix b/changes/changelog.d/764.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..114f602b5744cf750341a5d9c995173de638854b
--- /dev/null
+++ b/changes/changelog.d/764.bugfix
@@ -0,0 +1 @@
+Fixed broken sample apache configuration (#764)
diff --git a/changes/changelog.d/765.bugfix b/changes/changelog.d/765.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..79872e1ed5bd06c9d95398e37032b9abd2ba00da
--- /dev/null
+++ b/changes/changelog.d/765.bugfix
@@ -0,0 +1 @@
+Include disc number in Subsonic responses (#765)
diff --git a/changes/changelog.d/766.enhancement b/changes/changelog.d/766.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..7aca9fb1c041d8119f52db134b24ecc306920945
--- /dev/null
+++ b/changes/changelog.d/766.enhancement
@@ -0,0 +1 @@
+Added title on hover for truncated content (#766)
diff --git a/changes/changelog.d/767.bugfix b/changes/changelog.d/767.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..8a2bbdb86c9442373863c36323517b5db4e2f0d0
--- /dev/null
+++ b/changes/changelog.d/767.bugfix
@@ -0,0 +1 @@
+Fixed broken Activity and Actor modules in django admin (#767)
diff --git a/deploy/apache.conf b/deploy/apache.conf
index 52eee9533308ff3f4a51a2df019d7026e582d296..e1409a627d3d4e76ec4a1901ace204ddf6efb374 100644
--- a/deploy/apache.conf
+++ b/deploy/apache.conf
@@ -65,7 +65,9 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
    </Proxy>
 
    # Activating WebSockets
-   ProxyPass "/api/v1/activity" ${funkwhale-api-ws}/api/v1/activity
+   <Location "/api/v1/activity">
+	   ProxyPass ${funkwhale-api-ws}/api/v1/activity
+   </Location>
 
    <Location "/">
       # similar to nginx 'client_max_body_size 100M;'
@@ -90,13 +92,19 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
       ProxyPassReverse ${funkwhale-api}/.well-known/
    </Location>
 
-   ProxyPass "/front" "!"
+   <Location "/front">
+      ProxyPass  "!"
+   </Location>
    Alias /front /srv/funkwhale/front/dist
 
-   ProxyPass "/media" "!"
+   <Location "/media">
+      ProxyPass  "!"
+   </Location>
    Alias /media /srv/funkwhale/data/media
 
-   ProxyPass "/staticfiles" "!"
+   <Location "/staticfiles">
+      ProxyPass  "!"
+   </Location>
    Alias /staticfiles /srv/funkwhale/data/static
 
    # Setting appropriate access levels to serve frontend
diff --git a/dev.yml b/dev.yml
index d5170f61ebe4ed0e5825fed1d3c0a8208755bee2..d0ffc62e433b3106cc08e91245c0f0d3b75b0f07 100644
--- a/dev.yml
+++ b/dev.yml
@@ -52,7 +52,7 @@ services:
     command: python /app/manage.py runserver 0.0.0.0:${FUNKWHALE_API_PORT-5000}
     volumes:
       - ./api:/app
-      - "${MUSIC_DIRECTORY_PATH-./data/music}:/music:ro"
+      - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro"
     environment:
       - "FUNKWHALE_HOSTNAME=${FUNKWHALE_HOSTNAME-localhost}"
       - "FUNKWHALE_HOSTNAME_SUFFIX=funkwhale.test"
@@ -87,7 +87,7 @@ services:
       - "CACHE_URL=redis://redis:6379/0"
     volumes:
       - ./api:/app
-      - "${MUSIC_DIRECTORY_PATH-./data/music}:/music:ro"
+      - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro"
     networks:
       - internal
   nginx:
@@ -112,7 +112,7 @@ services:
     volumes:
       - ./docker/nginx/conf.dev:/etc/nginx/nginx.conf.template:ro
       - ./docker/nginx/entrypoint.sh:/entrypoint.sh:ro
-      - "${MUSIC_DIRECTORY_PATH-./data/music}:/music:ro"
+      - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro"
       - ./deploy/funkwhale_proxy.conf:/etc/nginx/funkwhale_proxy.conf:ro
       - "${MEDIA_ROOT-./api/funkwhale_api/media}:/protected/media:ro"
     networks:
diff --git a/docs/backup.rst b/docs/backup.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d474678ae3ba48a5c0a51b9114200673148c894e
--- /dev/null
+++ b/docs/backup.rst
@@ -0,0 +1,79 @@
+Backup your Funkwhale instance
+==============================
+
+.. note::
+
+    Before upgrading your instance, we strongly advise you to make at least a database backup. Ideally, you should make a full backup, including the database and the media files.
+
+
+Docker setup
+------------
+
+If you've followed the setup instructions in :doc:`../installation/docker`, here is the backup path:
+
+Multi-container installation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Backup the db
+^^^^^^^^^^^^^
+
+On docker setups, you have to ``pg_dumpall`` in container ``funkwhale_postgres_1``:
+
+.. code-block:: shell
+
+   docker exec -t funkwhale_postgres_1 pg_dumpall -c -U postgres > dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql
+
+Backup the media files
+^^^^^^^^^^^^^^^^^^^^^^
+
+To backup docker data volumes, as the volumes are bound mounted to the host, the ``rsync`` way would go like this:
+
+.. code-block:: shell
+
+    rsync -avzhP /srv/funkwhale/data/media /path/to/your/backup/media
+    rsync -avzhP /srv/funkwhale/data/music /path/to/your/backup/music
+
+
+Backup the configuration files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+On docker setups, the configuration file is located at the root level:
+
+.. code-block:: shell
+
+    rsync -avzhP /srv/funkwhale/.env /path/to/your/backup/.env
+
+
+Non-docker setup
+----------------
+
+Backup the db
+^^^^^^^^^^^^^
+
+On non-docker setups, you have to ``pg_dump`` as user ``postgres``:
+
+.. code-block:: shell
+
+   sudo -u postgres -H pg_dump funkwhale > /path/to/your/backup/dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql
+
+Backup the media files
+^^^^^^^^^^^^^^^^^^^^^^
+
+A simple way to backup your media files is to use ``rsync``:
+
+.. code-block:: shell
+
+    rsync -avzhP /srv/funkwhale/data/media /path/to/your/backup/media
+    rsync -avzhP /srv/funkwhale/data/music /path/to/your/backup/music
+
+Backup the configuration files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: shell
+
+    rsync -avzhP /srv/funkwhale/config/.env /path/to/your/backup/.env
+
+.. note::
+   You may also want to backup your proxy configuration file.
+
+   For frequent backups, you may want to use deduplication and compression to keep the backup size low. In this case, a tool like ``borg`` will be more appropriate.
diff --git a/docs/users/index.rst b/docs/users/index.rst
index 86dbf62d5f1e7a886227936212d99ac0536bdac2..aa05555bd93f60256087c1d9f544f1c0660c1f3f 100644
--- a/docs/users/index.rst
+++ b/docs/users/index.rst
@@ -34,5 +34,5 @@ Troubleshooting Issues
 
 .. toctree::
    :maxdepth: 2
-   
+
    troubleshooting
diff --git a/docs/users/upload.rst b/docs/users/upload.rst
index c6f5646f0a3f2c0ac5481ec35ba56bef613fb872..1dfa4d76fb73f2ed3bf2a8ee70a3db69f908f4f6 100644
--- a/docs/users/upload.rst
+++ b/docs/users/upload.rst
@@ -1,5 +1,5 @@
-Uploading and removing content on Funkwhale
-===========================================
+Uploading Content To Funkwhale
+==============================
 
 To upload content to any Funkwhale instance, you need:
 
@@ -149,7 +149,7 @@ can vary depending on server load.
 Removing files
 --------------
 
-If you want to remove some of the files you have uploaded, visit ``/content/libraries/tracks/`` or click "Add content" in the sidebar then "Tracks" in the top menu. 
+If you want to remove some of the files you have uploaded, visit ``/content/libraries/tracks/`` or click "Add content" in the sidebar then "Tracks" in the top menu.
 Then select the files you want to delete using the checkboxes on the left ; you can filter the list of files using a search pattern.
 Finally, select "Delete" in the "Action" menu and click "Go".
 
diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue
index caa402cf32dee93916cf8256b440417413a78a48..213ab92ce9092cbe073422ce02386396fdd942ad 100644
--- a/front/src/components/Sidebar.vue
+++ b/front/src/components/Sidebar.vue
@@ -146,9 +146,11 @@
                   <img class="ui mini image" v-else src="../assets/audio/default-cover.png">
               </td>
               <td colspan="4">
-                  <button class="title reset ellipsis" :aria-label="labels.selectTrack">
+                  <button class="title reset ellipsis" :title="track.title" :aria-label="labels.selectTrack">
                     <strong>{{ track.title }}</strong><br />
-                    {{ track.artist.name }}
+                    <span>
+                      {{ track.artist.name }}
+                    </span>
                   </button>
               </td>
               <td>
diff --git a/front/src/components/audio/album/Card.vue b/front/src/components/audio/album/Card.vue
index d498e4e355d4caf517b1688f619ad2e7e8694e26..54ae9f254c180df940ed32dcf30837ee7a7b9225 100644
--- a/front/src/components/audio/album/Card.vue
+++ b/front/src/components/audio/album/Card.vue
@@ -10,7 +10,7 @@
         </div>
         <div class="meta">
           <span>
-            <router-link tag="span" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}">
+            <router-link :title="album.artist.name" tag="span" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}">
               <span v-translate="{artist: album.artist.name}" translate-context="Content/Album/Card" :translate-params="{artist: album.artist.name}">By %{ artist }</span>
             </router-link>
           </span><span class="time" v-if="album.release_date">– {{ album.release_date | year }}</span>
@@ -24,7 +24,7 @@
                 </td>
                 <td class="content-cell" colspan="5">
                   <track-favorite-icon :track="track"></track-favorite-icon>
-                  <router-link class="track discrete link" :to="{name: 'library.tracks.detail', params: {id: track.id }}">
+                  <router-link :title="track.title" class="track discrete link" :to="{name: 'library.tracks.detail', params: {id: track.id }}">
                     <template v-if="track.position">
                       {{ track.position }}.
                     </template>
diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue
index 9e018216c07906313d84447ca28f3177d80af772..b06cdcd75b126c3d5b7f68abce5923caf0c18a2a 100644
--- a/front/src/components/audio/artist/Card.vue
+++ b/front/src/components/audio/artist/Card.vue
@@ -15,7 +15,7 @@
                   <img class="ui mini image" v-else src="../../../assets/audio/default-cover.png">
                 </td>
                 <td colspan="4">
-                  <router-link class="discrete link" :to="{name: 'library.albums.detail', params: {id: album.id }}">
+                  <router-link :title="album.title" class="discrete link" :to="{name: 'library.albums.detail', params: {id: album.id }}">
                     <strong>{{ album.title }}</strong>
                   </router-link><br />
                   {{ album.tracks_count }} tracks
diff --git a/front/src/components/audio/track/Row.vue b/front/src/components/audio/track/Row.vue
index 690fe48f5b89ac50d566d0256dae6d09f7e8566e..abc4137bf397306b457cb19e8e9d39e6b21c0838 100644
--- a/front/src/components/audio/track/Row.vue
+++ b/front/src/components/audio/track/Row.vue
@@ -8,7 +8,7 @@
       <img class="ui mini image" v-else src="../../../assets/audio/default-cover.png">
     </td>
     <td colspan="6">
-      <router-link class="track" :to="{name: 'library.tracks.detail', params: {id: track.id }}">
+      <router-link class="track" :title="track.title" :to="{name: 'library.tracks.detail', params: {id: track.id }}">
         <template v-if="displayPosition && track.position">
           {{ track.position }}.
         </template>
@@ -16,21 +16,21 @@
       </router-link>
     </td>
     <td colspan="4">
-      <router-link v-if="track.artist.id === albumArtist.id" class="artist discrete link" :to="{name: 'library.artists.detail', params: {id: track.artist.id }}">
+      <router-link v-if="track.artist.id === albumArtist.id" :title="track.artist.name" class="artist discrete link" :to="{name: 'library.artists.detail', params: {id: track.artist.id }}">
         {{ track.artist.name }}
       </router-link>
       <template v-else>
-        <router-link class="artist discrete link" :to="{name: 'library.artists.detail', params: {id: albumArtist.id }}">
+        <router-link class="artist discrete link" :title="albumArtist.name" :to="{name: 'library.artists.detail', params: {id: albumArtist.id }}">
           {{ albumArtist.name }}
         </router-link>
          /
-         <router-link class="artist discrete link" :to="{name: 'library.artists.detail', params: {id: track.artist.id }}">
+         <router-link class="artist discrete link" :title="track.artist.name" :to="{name: 'library.artists.detail', params: {id: track.artist.id }}">
           {{ track.artist.name }}
         </router-link>
       </template>
     </td>
     <td colspan="4">
-      <router-link class="album discrete link" :to="{name: 'library.albums.detail', params: {id: track.album.id }}">
+      <router-link class="album discrete link" :title="track.album.title" :to="{name: 'library.albums.detail', params: {id: track.album.id }}">
         {{ track.album.title }}
       </router-link>
     </td>
diff --git a/front/src/components/playlists/PlaylistModal.vue b/front/src/components/playlists/PlaylistModal.vue
index a3ffbd51eea94f6ac710288871043c9d0dde96af..522f150fcaef5ab24e0db7f41b43c8f663eaca2f 100644
--- a/front/src/components/playlists/PlaylistModal.vue
+++ b/front/src/components/playlists/PlaylistModal.vue
@@ -43,7 +43,7 @@
                   class="ui icon basic small button"
                   :to="{name: 'library.playlists.detail', params: {id: playlist.id }, query: {mode: 'edit'}}"><i class="ui pencil icon"></i></router-link>
               </td>
-              <td>
+              <td :title="playlist.name">
                 <router-link :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">{{ playlist.name }}</router-link></td>
               <td><human-date :date="playlist.modification_date"></human-date></td>
               <td>{{ playlist.tracks_count }}</td>