diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 24b23b84b104ccc2bc9e5fa5860ed1c31ab152c2..0b68d19c005c9ed446a0ee7e321db7b736b50b7d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,6 +18,8 @@ test_api:
     - pip install -r requirements/test.txt
   script:
     - pytest
+  variables:
+    DATABASE_URL: "sqlite://"
 
   tags:
     - docker
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000000000000000000000000000000000000..6faa5d267cf4094c9220a2bc20e8c816f1d9e79c
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,19 @@
+Changelog
+=========
+
+
+0.2.5 (unreleased)
+------------------
+
+
+0.2.4 (2017-12-14)
+------------------
+
+Features:
+
+- Models: now store relese group mbid on Album model (#7)
+- Models: now bind import job to track files (#44)
+
+Bugfixes:
+
+- Library: fixen broken "play all albums" button on artist cards in Artist browsing view (#43)
diff --git a/api/compose/django/entrypoint.sh b/api/compose/django/entrypoint.sh
index 98b3681e1cca70ee115d09f7cc2834e686de51bf..7e789968ba601f901bd73ca56a2728af1abd1d2d 100755
--- a/api/compose/django/entrypoint.sh
+++ b/api/compose/django/entrypoint.sh
@@ -4,7 +4,7 @@ set -e
 # Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple
 # environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint
 # does all this for us.
-export REDIS_URL=redis://redis:6379/0
+export CACHE_URL=redis://redis:6379/0
 
 # the official postgres image uses 'postgres' as default user if not set explictly.
 if [ -z "$POSTGRES_ENV_POSTGRES_USER" ]; then
@@ -13,7 +13,7 @@ fi
 
 export DATABASE_URL=postgres://$POSTGRES_ENV_POSTGRES_USER:$POSTGRES_ENV_POSTGRES_PASSWORD@postgres:5432/$POSTGRES_ENV_POSTGRES_USER
 
-export CELERY_BROKER_URL=$REDIS_URL
+export CELERY_BROKER_URL=$CACHE_URL
 
 # we copy the frontend files, if any so we can serve them from the outside
 if [ -d "frontend" ]; then
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 6a2299bb4c619175082d1490ead7eeffd088fbb5..b10a0310c162a1b94df427de5b17ea2f2d5b56d8 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -124,7 +124,7 @@ MANAGERS = ADMINS
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
 DATABASES = {
     # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
-    'default': env.db("DATABASE_URL", default="postgresql://postgres@postgres/postgres"),
+    'default': env.db("DATABASE_URL"),
 }
 DATABASES['default']['ATOMIC_REQUESTS'] = True
 #
@@ -199,7 +199,7 @@ CRISPY_TEMPLATE_PACK = 'bootstrap3'
 # STATIC FILE CONFIGURATION
 # ------------------------------------------------------------------------------
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
-STATIC_ROOT = str(ROOT_DIR('staticfiles'))
+STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR('staticfiles')))
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
 STATIC_URL = env("STATIC_URL", default='/staticfiles/')
@@ -218,12 +218,10 @@ STATICFILES_FINDERS = (
 # MEDIA CONFIGURATION
 # ------------------------------------------------------------------------------
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
-MEDIA_ROOT = str(APPS_DIR('media'))
-
-
+MEDIA_ROOT = env("MEDIA_ROOT", default=str(APPS_DIR('media')))
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
-MEDIA_URL = '/media/'
+MEDIA_URL = env("MEDIA_URL", default='/media/')
 
 # URL Configuration
 # ------------------------------------------------------------------------------
@@ -253,26 +251,24 @@ LOGIN_URL = 'account_login'
 # SLUGLIFIER
 AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify'
 
-########## CELERY
-INSTALLED_APPS += ('funkwhale_api.taskapp.celery.CeleryConfig',)
-# if you are not using the django database broker (e.g. rabbitmq, redis, memcached), you can remove the next line.
-INSTALLED_APPS += ('kombu.transport.django',)
-BROKER_URL = env("CELERY_BROKER_URL", default='django://')
-########## END CELERY
-
-
+CACHE_DEFAULT = "redis://127.0.0.1:6379/0"
 CACHES = {
-    "default": {
-        "BACKEND": "django_redis.cache.RedisCache",
-        "LOCATION": "{0}/{1}".format(env.cache_url('REDIS_URL', default="redis://127.0.0.1:6379"), 0),
-        "OPTIONS": {
-            "CLIENT_CLASS": "django_redis.client.DefaultClient",
-            "IGNORE_EXCEPTIONS": True,  # mimics memcache behavior.
-                                        # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
-        }
-    }
+    "default": env.cache_url('CACHE_URL', default=CACHE_DEFAULT)
 }
 
+CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache"
+CACHES["default"]["OPTIONS"] = {
+    "CLIENT_CLASS": "django_redis.client.DefaultClient",
+    "IGNORE_EXCEPTIONS": True,  # mimics memcache behavior.
+                                # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
+}
+
+
+########## CELERY
+INSTALLED_APPS += ('funkwhale_api.taskapp.celery.CeleryConfig',)
+BROKER_URL = env(
+    "CELERY_BROKER_URL", default=env('CACHE_URL', default=CACHE_DEFAULT))
+########## END CELERY
 # Location of root django.contrib.admin URL, use {% url 'admin:index' %}
 ADMIN_URL = r'^admin/'
 # Your common stuff: Below this line define 3rd party library settings
@@ -336,3 +332,8 @@ MUSICBRAINZ_CACHE_DURATION = env.int(
 )
 
 CACHALOT_ENABLED = env.bool('CACHALOT_ENABLED', default=True)
+
+
+# Custom Admin URL, use {% url 'admin:index' %}
+ADMIN_URL = env('DJANGO_ADMIN_URL', default='^api/admin/')
+CSRF_USE_SESSIONS = True
diff --git a/api/config/settings/production.py b/api/config/settings/production.py
index e8a05bd3b6c5a34e442480539e39716a3f1baad4..a132076c72ae90c2d71f2480b9346a34f8873c2a 100644
--- a/api/config/settings/production.py
+++ b/api/config/settings/production.py
@@ -54,7 +54,7 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
 # ------------------------------------------------------------------------------
 # Hosts/domain names that are valid for this site
 # See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts
-ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['funkwhale.io'])
+ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
 # END SITE CONFIGURATION
 
 INSTALLED_APPS += ("gunicorn", )
@@ -65,10 +65,6 @@ INSTALLED_APPS += ("gunicorn", )
 # ------------------------
 DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
 
-# URL that handles the media served from MEDIA_ROOT, used for managing
-# stored files.
-MEDIA_URL = '/media/'
-
 # Static Assets
 # ------------------------
 STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
@@ -92,11 +88,6 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
         'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ]),
 ]
 
-# DATABASE CONFIGURATION
-# ------------------------------------------------------------------------------
-# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
-DATABASES['default'] = env.db("DATABASE_URL")
-
 # CACHING
 # ------------------------------------------------------------------------------
 # Heroku URL does not pass the DB number, so we parse it in
@@ -151,7 +142,5 @@ LOGGING = {
     }
 }
 
-# Custom Admin URL, use {% url 'admin:index' %}
-ADMIN_URL = env('DJANGO_ADMIN_URL')
 
 # Your production stuff: Below this line define 3rd party library settings
diff --git a/api/config/settings/test.py b/api/config/settings/test.py
index b8dd89b049724a69465e428cf4ed3d120cf5a0a8..7d02c417b39c675a4f7afef254fb43fdcecec3cc 100644
--- a/api/config/settings/test.py
+++ b/api/config/settings/test.py
@@ -22,6 +22,9 @@ CACHES = {
         'LOCATION': ''
     }
 }
+INSTALLED_APPS += ('kombu.transport.django',)
+BROKER_URL = 'django://'
+
 # TESTING
 # ------------------------------------------------------------------------------
 TEST_RUNNER = 'django.test.runner.DiscoverRunner'
diff --git a/api/funkwhale_api/__init__.py b/api/funkwhale_api/__init__.py
index d961973d34db055563899a8dc956514abec7f967..92ed1ff3394460b744c46737a6a1098a4acc38c9 100644
--- a/api/funkwhale_api/__init__.py
+++ b/api/funkwhale_api/__init__.py
@@ -1,3 +1,3 @@
 # -*- coding: utf-8 -*-
-__version__ = '0.2.3'
+__version__ = '0.2.4'
 __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
diff --git a/api/funkwhale_api/contrib/sites/migrations/0003_auto_20171214_2205.py b/api/funkwhale_api/contrib/sites/migrations/0003_auto_20171214_2205.py
new file mode 100644
index 0000000000000000000000000000000000000000..14a9ec1a8a49ded7ec0458db9f8a9a2f6388dbe5
--- /dev/null
+++ b/api/funkwhale_api/contrib/sites/migrations/0003_auto_20171214_2205.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2017-12-14 22:05
+from __future__ import unicode_literals
+
+import django.contrib.sites.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('sites', '0002_set_site_domain_and_name'),
+    ]
+
+    operations = [
+        migrations.AlterModelManagers(
+            name='site',
+            managers=[
+                ('objects', django.contrib.sites.models.SiteManager()),
+            ],
+        ),
+        migrations.AlterField(
+            model_name='site',
+            name='domain',
+            field=models.CharField(max_length=100, unique=True, validators=[django.contrib.sites.models._simple_domain_name_validator], verbose_name='domain name'),
+        ),
+    ]
diff --git a/api/funkwhale_api/music/admin.py b/api/funkwhale_api/music/admin.py
index a6a7f94f3b90abae353bc85f2a51734db6a31edb..524b85386a6c4b41482d2733719757b2fdb18dbf 100644
--- a/api/funkwhale_api/music/admin.py
+++ b/api/funkwhale_api/music/admin.py
@@ -2,30 +2,35 @@ from django.contrib import admin
 
 from . import models
 
+
 @admin.register(models.Artist)
 class ArtistAdmin(admin.ModelAdmin):
     list_display = ['name', 'mbid', 'creation_date']
     search_fields = ['name', 'mbid']
 
+
 @admin.register(models.Album)
 class AlbumAdmin(admin.ModelAdmin):
     list_display = ['title', 'artist', 'mbid', 'release_date', 'creation_date']
     search_fields = ['title', 'artist__name', 'mbid']
     list_select_related = True
 
+
 @admin.register(models.Track)
 class TrackAdmin(admin.ModelAdmin):
     list_display = ['title', 'artist', 'album', 'mbid']
     search_fields = ['title', 'artist__name', 'album__title', 'mbid']
     list_select_related = True
 
+
 @admin.register(models.ImportBatch)
 class ImportBatchAdmin(admin.ModelAdmin):
     list_display = ['creation_date', 'status']
 
+
 @admin.register(models.ImportJob)
 class ImportJobAdmin(admin.ModelAdmin):
-    list_display = ['source', 'batch', 'status', 'mbid']
+    list_display = ['source', 'batch', 'track_file', 'status', 'mbid']
     list_select_related = True
     search_fields = ['source', 'batch__pk', 'mbid']
     list_filter = ['status']
diff --git a/api/funkwhale_api/music/migrations/0013_auto_20171213_2211.py b/api/funkwhale_api/music/migrations/0013_auto_20171213_2211.py
new file mode 100644
index 0000000000000000000000000000000000000000..00ccbb6218d303d95b65273c058f596dbd7e3da6
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0013_auto_20171213_2211.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2017-12-13 22:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0012_auto_20161122_1905'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='importjob',
+            options={'ordering': ('id',)},
+        ),
+        migrations.AlterModelOptions(
+            name='track',
+            options={'ordering': ['album', 'position']},
+        ),
+        migrations.AddField(
+            model_name='album',
+            name='release_group_id',
+            field=models.UUIDField(blank=True, null=True),
+        ),
+    ]
diff --git a/api/funkwhale_api/music/migrations/0014_importjob_track_file.py b/api/funkwhale_api/music/migrations/0014_importjob_track_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..6950fd3c1d55d0f5352d04cf217068d30b127381
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0014_importjob_track_file.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2017-12-14 21:14
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0013_auto_20171213_2211'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='importjob',
+            name='track_file',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='music.TrackFile'),
+        ),
+    ]
diff --git a/api/funkwhale_api/music/migrations/0015_bind_track_file_to_import_job.py b/api/funkwhale_api/music/migrations/0015_bind_track_file_to_import_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..edb5e6470dc87de3c282ea362d1e247adb7c75cd
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0015_bind_track_file_to_import_job.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+import os
+
+from django.db import migrations, models
+from funkwhale_api.common.utils import rename_file
+
+
+def bind_jobs(apps, schema_editor):
+    TrackFile = apps.get_model("music", "TrackFile")
+    ImportJob = apps.get_model("music", "ImportJob")
+
+    for job in ImportJob.objects.all().only('mbid'):
+        f = TrackFile.objects.filter(track__mbid=job.mbid).first()
+        if not f:
+            print('No file for mbid {}'.format(job.mbid))
+            continue
+        job.track_file = f
+        job.save(update_fields=['track_file'])
+
+
+def rewind(apps, schema_editor):
+    pass
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0014_importjob_track_file'),
+    ]
+
+    operations = [
+        migrations.RunPython(bind_jobs, rewind),
+    ]
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 596f890b7e43b609d036c4a46040973544eaf2fd..95a47fd4a7ac6fabe2cd842d6ed2d9994e26b1bd 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -110,13 +110,14 @@ class Album(APIModelMixin):
     title = models.CharField(max_length=255)
     artist = models.ForeignKey(Artist, related_name='albums')
     release_date = models.DateField(null=True)
+    release_group_id = models.UUIDField(null=True, blank=True)
     cover = VersatileImageField(upload_to='albums/covers/%Y/%m/%d', null=True, blank=True)
     TYPE_CHOICES = (
         ('album', 'Album'),
     )
     type = models.CharField(choices=TYPE_CHOICES, max_length=30, default='album')
 
-    api_includes = ['artist-credits', 'recordings', 'media']
+    api_includes = ['artist-credits', 'recordings', 'media', 'release-groups']
     api = musicbrainz.api.releases
     musicbrainz_model = 'release'
     musicbrainz_mapping = {
@@ -127,6 +128,10 @@ class Album(APIModelMixin):
             'musicbrainz_field_name': 'release-list',
             'converter': lambda v: int(v[0]['medium-list'][0]['position']),
         },
+        'release_group_id': {
+            'musicbrainz_field_name': 'release-group',
+            'converter': lambda v: v['id'],
+        },
         'title': {
             'musicbrainz_field_name': 'title',
         },
@@ -388,6 +393,8 @@ class ImportBatch(models.Model):
 
 class ImportJob(models.Model):
     batch = models.ForeignKey(ImportBatch, related_name='jobs')
+    track_file = models.ForeignKey(
+        TrackFile, related_name='jobs', null=True, blank=True)
     source = models.URLField()
     mbid = models.UUIDField(editable=False)
     STATUS_CHOICES = (
@@ -408,10 +415,12 @@ class ImportJob(models.Model):
             elif track.files.count() > 0:
                 return
 
-            track_file = track_file or TrackFile(track=track, source=self.source)
+            track_file = track_file or TrackFile(
+                track=track, source=self.source)
             track_file.download_file()
             track_file.save()
             self.status = 'finished'
+            self.track_file = track_file
             self.save()
             return track.pk
 
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index 744115f866ff43137be1b61cf47671fec4f414ae..43daf9d5a19404309b3ccf1f85e2e4c4713a9840 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -9,35 +9,26 @@ class TagSerializer(serializers.ModelSerializer):
         model = Tag
         fields = ('id', 'name', 'slug')
 
+
 class SimpleArtistSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Artist
         fields = ('id', 'mbid', 'name')
 
+
 class ArtistSerializer(serializers.ModelSerializer):
     tags = TagSerializer(many=True, read_only=True)
     class Meta:
         model = models.Artist
         fields = ('id', 'mbid', 'name', 'tags')
 
-class ImportJobSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = models.ImportJob
-        fields = ('id', 'mbid', 'source', 'status')
-
-class ImportBatchSerializer(serializers.ModelSerializer):
-    jobs = ImportJobSerializer(many=True, read_only=True)
-    class Meta:
-        model = models.ImportBatch
-        fields = ('id', 'jobs', 'status', 'creation_date')
-
 
 class TrackFileSerializer(serializers.ModelSerializer):
     path = serializers.SerializerMethodField()
 
     class Meta:
         model = models.TrackFile
-        fields = ('id', 'path', 'duration', 'source', 'filename')
+        fields = ('id', 'path', 'duration', 'source', 'filename', 'track')
 
     def get_path(self, o):
         request = self.context.get('request')
@@ -46,12 +37,14 @@ class TrackFileSerializer(serializers.ModelSerializer):
             url = request.build_absolute_uri(url)
         return url
 
+
 class SimpleAlbumSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.Album
         fields = ('id', 'mbid', 'title', 'release_date', 'cover')
 
+
 class AlbumSerializer(serializers.ModelSerializer):
     tags = TagSerializer(many=True, read_only=True)
     class Meta:
@@ -81,6 +74,7 @@ class TrackSerializer(LyricsMixin):
             'position',
             'lyrics')
 
+
 class TrackSerializerNested(LyricsMixin):
     artist = ArtistSerializer()
     files = TrackFileSerializer(many=True, read_only=True)
@@ -90,6 +84,7 @@ class TrackSerializerNested(LyricsMixin):
         model = models.Track
         fields = ('id', 'mbid', 'title', 'artist', 'files', 'album', 'tags', 'lyrics')
 
+
 class AlbumSerializerNested(serializers.ModelSerializer):
     tracks = TrackSerializer(many=True, read_only=True)
     artist = SimpleArtistSerializer()
@@ -99,6 +94,7 @@ class AlbumSerializerNested(serializers.ModelSerializer):
         model = models.Album
         fields = ('id', 'mbid', 'title', 'cover', 'artist', 'release_date', 'tracks', 'tags')
 
+
 class ArtistSerializerNested(serializers.ModelSerializer):
     albums = AlbumSerializerNested(many=True, read_only=True)
     tags = TagSerializer(many=True, read_only=True)
@@ -111,3 +107,17 @@ class LyricsSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Lyrics
         fields = ('id', 'work', 'content', 'content_rendered')
+
+
+class ImportJobSerializer(serializers.ModelSerializer):
+    track_file = TrackFileSerializer(read_only=True)
+    class Meta:
+        model = models.ImportJob
+        fields = ('id', 'mbid', 'source', 'status', 'track_file')
+
+
+class ImportBatchSerializer(serializers.ModelSerializer):
+    jobs = ImportJobSerializer(many=True, read_only=True)
+    class Meta:
+        model = models.ImportBatch
+        fields = ('id', 'jobs', 'status', 'creation_date')
diff --git a/api/funkwhale_api/music/tests/factories.py b/api/funkwhale_api/music/tests/factories.py
index ea680b3bd5d4da58e63f5ee4c861fec7972117f4..b554e3e14a934bc26281130dc869ec09907dc80f 100644
--- a/api/funkwhale_api/music/tests/factories.py
+++ b/api/funkwhale_api/music/tests/factories.py
@@ -1,6 +1,8 @@
 import factory
 import os
 
+from funkwhale_api.users.tests.factories import UserFactory
+
 SAMPLES_PATH = os.path.dirname(os.path.abspath(__file__))
 
 
@@ -18,6 +20,7 @@ class AlbumFactory(factory.django.DjangoModelFactory):
     release_date = factory.Faker('date')
     cover = factory.django.ImageField()
     artist = factory.SubFactory(ArtistFactory)
+    release_group_id = factory.Faker('uuid4')
 
     class Meta:
         model = 'music.Album'
@@ -41,3 +44,18 @@ class TrackFileFactory(factory.django.DjangoModelFactory):
 
     class Meta:
         model = 'music.TrackFile'
+
+
+class ImportBatchFactory(factory.django.DjangoModelFactory):
+    submitted_by = factory.SubFactory(UserFactory)
+
+    class Meta:
+        model = 'music.ImportBatch'
+
+
+class ImportJobFactory(factory.django.DjangoModelFactory):
+    batch = factory.SubFactory(ImportBatchFactory)
+    source = factory.Faker('url')
+
+    class Meta:
+        model = 'music.ImportJob'
diff --git a/api/funkwhale_api/music/tests/test_models.py b/api/funkwhale_api/music/tests/test_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b43e46382ad28d4bbf382fef854c39b8cae5d48
--- /dev/null
+++ b/api/funkwhale_api/music/tests/test_models.py
@@ -0,0 +1,51 @@
+import pytest
+
+from funkwhale_api.music import models
+from funkwhale_api.music import importers
+from . import factories
+
+
+def test_can_store_release_group_id_on_album(db):
+    album = factories.AlbumFactory()
+    assert album.release_group_id is not None
+
+
+def test_import_album_stores_release_group(db):
+
+    album_data = {
+        "artist-credit": [
+            {
+                "artist": {
+                    "disambiguation": "George Shaw",
+                    "id": "62c3befb-6366-4585-b256-809472333801",
+                    "name": "Adhesive Wombat",
+                    "sort-name": "Wombat, Adhesive"
+                }
+            }
+        ],
+        "artist-credit-phrase": "Adhesive Wombat",
+        "country": "XW",
+        "date": "2013-06-05",
+        "id": "a50d2a81-2a50-484d-9cb4-b9f6833f583e",
+        "status": "Official",
+        "title": "Marsupial Madness",
+        'release-group': {'id': '447b4979-2178-405c-bfe6-46bf0b09e6c7'}
+    }
+    artist = factories.ArtistFactory(
+        mbid=album_data['artist-credit'][0]['artist']['id']
+    )
+    cleaned_data = models.Album.clean_musicbrainz_data(album_data)
+    album = importers.load(models.Album, cleaned_data, album_data, import_hooks=[])
+
+    assert album.release_group_id == album_data['release-group']['id']
+    assert album.artist == artist
+
+
+def test_import_job_is_bound_to_track_file(db, mocker):
+    track = factories.TrackFactory()
+    job = factories.ImportJobFactory(mbid=track.mbid)
+
+    mocker.patch('funkwhale_api.music.models.TrackFile.download_file')
+    job.run()
+    job.refresh_from_db()
+    assert job.track_file.track == track
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index d149b5d1b9b937955d89debc33ecf2689d64c9e5..72982e4c5b916360e6f02d811507757c51dcb61a 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -72,7 +72,10 @@ class AlbumViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet):
 
 
 class ImportBatchViewSet(viewsets.ReadOnlyModelViewSet):
-    queryset = models.ImportBatch.objects.all().order_by('-creation_date')
+    queryset = (
+        models.ImportBatch.objects.all()
+                          .prefetch_related('jobs__track_file')
+                          .order_by('-creation_date'))
     serializer_class = serializers.ImportBatchSerializer
 
     def get_queryset(self):
diff --git a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
index a34e36b4b322753a38928545bd30cebdc83434f5..a4bfe555ed56c3959dc90b598cc52a3b01420faf 100644
--- a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
+++ b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
@@ -1,6 +1,6 @@
 import glob
 from django.core.management.base import BaseCommand, CommandError
-from funkwhale_api.providers.audiofile import importer
+from funkwhale_api.providers.audiofile import tasks
 
 
 class Command(BaseCommand):
@@ -61,7 +61,7 @@ class Command(BaseCommand):
         for path in matching:
             self.stdout.write(message.format(path))
             try:
-                importer.from_path(path)
+                tasks.from_path(path)
             except Exception as e:
                 self.stdout.write('Error: {}'.format(e))
 
diff --git a/api/funkwhale_api/providers/audiofile/importer.py b/api/funkwhale_api/providers/audiofile/tasks.py
similarity index 100%
rename from api/funkwhale_api/providers/audiofile/importer.py
rename to api/funkwhale_api/providers/audiofile/tasks.py
diff --git a/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py b/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
index 4a91a36ebfc6d76fb1cdd7887d6d24b59485c991..f8d36986a3170fea19b6be1b9030b354fddcba29 100644
--- a/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
+++ b/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
@@ -3,7 +3,7 @@ import datetime
 import unittest
 from test_plus.test import TestCase
 
-from funkwhale_api.providers.audiofile import importer
+from funkwhale_api.providers.audiofile import tasks
 
 DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
@@ -27,7 +27,7 @@ class TestAudioFile(TestCase):
             return_value='OggVorbis',
         )
         with m1, m2:
-            track_file = importer.from_path(
+            track_file = tasks.from_path(
                 os.path.join(DATA_DIR, 'dummy_file.ogg'))
 
         self.assertEqual(
diff --git a/api/funkwhale_api/users/migrations/0002_auto_20171214_2205.py b/api/funkwhale_api/users/migrations/0002_auto_20171214_2205.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bbbaa62bcc44591449333ef4faa82e3fdd49c80
--- /dev/null
+++ b/api/funkwhale_api/users/migrations/0002_auto_20171214_2205.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2017-12-14 22:05
+from __future__ import unicode_literals
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterModelManagers(
+            name='user',
+            managers=[
+                ('objects', django.contrib.auth.models.UserManager()),
+            ],
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='username',
+            field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
+        ),
+    ]
diff --git a/api/manage.py b/api/manage.py
index 7b367ffeb1e6a87c98f477f05bf70b0c89347ee8..d99574ebe2ac8b7f8381d768b4651cb002561e1f 100755
--- a/api/manage.py
+++ b/api/manage.py
@@ -2,8 +2,11 @@
 import os
 import sys
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
 if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
+
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
 
     from django.core.management import execute_from_command_line
 
diff --git a/api/requirements.apt b/api/requirements.apt
index 68eaf2881f7ef5b2ef435afc970b0dec141b9f6c..86c7d8b20c99e3cc60c7b06c40f49ac8fc4275d4 100644
--- a/api/requirements.apt
+++ b/api/requirements.apt
@@ -1,46 +1,9 @@
-##basic build dependencies of various Django apps for Ubuntu 14.04
-#build-essential metapackage install: make, gcc, g++,
 build-essential
-#required to translate
 gettext
-#python-dev
-
-##shared dependencies of:
-##Pillow, pylibmc
 zlib1g-dev
-
-##Postgresql and psycopg2 dependencies
+libjpeg-dev
+zlib1g-dev
 libpq-dev
 postgresql-client
-##Pillow dependencies
-#libtiff4-dev
-#libjpeg8-dev
-#libfreetype6-dev
-#liblcms1-dev
-#libwebp-dev
-
-
-##django-extensions
-#graphviz-dev
-
-##hitch
-#python-setuptools
-#python3-dev
-#python-virtualenv
-#python-pip
-#firefox
-#automake
-#libtool
-#libreadline6
-#libreadline6-dev
-#libreadline-dev
-libsqlite3-dev
-#libxml2
-#libxml2-dev
-#libssl-dev
-#libbz2-dev
-#wget
-#curl
-#llvm
-
 libav-tools
+python3-dev
diff --git a/api/requirements/test.txt b/api/requirements/test.txt
index a26cf5bdbb1a8b60209fb7632c9acf3b3f9c0b08..7c304bbdb430bb46647c713a6e0cd1d85ed4111e 100644
--- a/api/requirements/test.txt
+++ b/api/requirements/test.txt
@@ -4,8 +4,8 @@
 
 flake8==2.5.0
 model-mommy==1.3.2
-tox==2.7.0
 pytest
 pytest-django
+pytest-mock
 pytest-sugar
 pytest-xdist
diff --git a/api/test.yml b/api/test.yml
index 6215e27de0a6e237d119f6a0a3fa7eff4ae0dd7e..bd3a98e457de5cf7610caf1ac5cef4dfd520715f 100644
--- a/api/test.yml
+++ b/api/test.yml
@@ -4,3 +4,5 @@ test:
   command: pytest
   volumes:
     - .:/app
+  environment:
+    - "DATABASE_URL=sqlite://"
diff --git a/deploy/env.prod.sample b/deploy/env.prod.sample
index fd3b4327f7415ce2f18fa2a21fde4e30de73ca54..9cbe278e827e96b015945b7a3beac1c6d1606d63 100644
--- a/deploy/env.prod.sample
+++ b/deploy/env.prod.sample
@@ -1,13 +1,21 @@
-# If you're tweaking this file from the template, ensure you edit at lest the
+# If you're tweaking this file from the template, ensure you edit at least the
 # following variables:
 # - DJANGO_SECRET_KEY
 # - DJANGO_ALLOWED_HOSTS
 
+# Additionaly, on non-docker setup, you'll also have to tweak/uncomment those
+# variables:
+# - DATABASE_URL
+# - CACHE_URL
+# - STATIC_ROOT
+# - MEDIA_ROOT
+
 # Docker only
 # -----------
 
 # The tag of the image we should use
 # (it will be interpolated in docker-compose file)
+# You can comment or ignore this if you're not using docker
 FUNKWHALE_VERSION=latest
 
 
@@ -17,26 +25,52 @@ FUNKWHALE_VERSION=latest
 # Set this variables to bind the API server to another interface/port
 # example: FUNKWHALE_API_IP=0.0.0.0
 # example: FUNKWHALE_API_PORT=5678
-FUNKWHALE_API_IP=
-FUNKWHALE_API_PORT=
+FUNKWHALE_API_IP=127.0.0.1
+FUNKWHALE_API_PORT=5000
+
 
 # API/Django configuration
 
-# which settings module should django use?
-# You don't have to touch this unless you really know what you're doing
-DJANGO_SETTINGS_MODULE=config.settings.production
+# Database configuration
+# Examples:
+#  DATABASE_URL=postgresql://<user>:<password>@<host>:<port>/<database>
+#  DATABASE_URL=postgresql://funkwhale:passw0rd@localhost:5432/funkwhale_database
+# Use the next one if you followed Debian installation guide
+# DATABASE_URL=postgresql://funkwhale@:5432/funkwhale
 
-# Generate one using `openssl rand -base64 45`, for example
-DJANGO_SECRET_KEY=
+# Cache configuration
+# Examples:
+#  CACHE_URL=redis://<host>:<port>/<database>
+#  CACHE_URL=redis://localhost:6379/0
+# Use the next one if you followed Debian installation guide
+# CACHE_URL=redis://127.0.0.1:6379/0
+
+# Where media files (such as album covers or audio tracks) should be stored
+# on your system?
+# (Ensure this directory actually exists)
+# MEDIA_ROOT=/srv/funkwhale/data/media
 
-# You don't have to edit this
-DJANGO_ADMIN_URL=^api/admin/
+# Where static files (such as API css or icons) should be compiled
+# on your system?
+# (Ensure this directory actually exists)
+# STATIC_ROOT=/srv/funkwhale/data/static
 
 # Update it to match the domain that will be used to reach your funkwhale
 # instance
 # Example: DJANGO_ALLOWED_HOSTS=funkwhale.yourdomain.com
 DJANGO_ALLOWED_HOSTS=yourdomain
 
+# which settings module should django use?
+# You don't have to touch this unless you really know what you're doing
+DJANGO_SETTINGS_MODULE=config.settings.production
+
+# Generate one using `openssl rand -base64 45`, for example
+DJANGO_SECRET_KEY=
+
+# You don't have to edit this, but you can put the admin on another URL if you
+# want to
+# DJANGO_ADMIN_URL=^api/admin/
+
 # If True, unauthenticated users won't be able to query the API
 API_AUTHENTICATION_REQUIRED=True
 
diff --git a/deploy/funkwhale-server.service b/deploy/funkwhale-server.service
new file mode 100644
index 0000000000000000000000000000000000000000..7ef6e389748bac6c972fbd0644412ceb375cb8e5
--- /dev/null
+++ b/deploy/funkwhale-server.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Funkwhale application server
+After=redis.service postgresql.service
+PartOf=funkwhale.target
+
+[Service]
+User=funkwhale
+# adapt this depending on the path of your funkwhale installation
+WorkingDirectory=/srv/funkwhale/api
+EnvironmentFile=/srv/funkwhale/config/.env
+ExecStart=/srv/funkwhale/virtualenv/bin/gunicorn config.wsgi:application -b ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}
+
+[Install]
+WantedBy=multi-user.target
diff --git a/deploy/funkwhale-worker.service b/deploy/funkwhale-worker.service
new file mode 100644
index 0000000000000000000000000000000000000000..2a25c2a1b70a4e58a68834dc626b38ebc23a426b
--- /dev/null
+++ b/deploy/funkwhale-worker.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Funkwhale celery worker
+After=redis.service postgresql.service
+PartOf=funkwhale.target
+
+[Service]
+User=funkwhale
+# adapt this depending on the path of your funkwhale installation
+WorkingDirectory=/srv/funkwhale/api
+EnvironmentFile=/srv/funkwhale/config/.env
+ExecStart=/srv/funkwhale/virtualenv/bin/python manage.py celery worker
+
+[Install]
+WantedBy=multi-user.target
diff --git a/deploy/funkwhale.target b/deploy/funkwhale.target
new file mode 100644
index 0000000000000000000000000000000000000000..a920c7e34c67d31c8bb295c254d9cddc899da2a2
--- /dev/null
+++ b/deploy/funkwhale.target
@@ -0,0 +1,3 @@
+[Unit]
+Description=Funkwhale
+Wants=funkwhale-server.service funkwhale-worker.service
diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index a85230ae8d6b9b629760154117584aef08d949fe..90b0f000e256db13959c19658c3bbff6d26c7676 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -1,29 +1,39 @@
+# Ensure you update at least the server_name variables to match your own
+# domain
+
 upstream funkwhale-api {
     # depending on your setup, you may want to udpate this
     server localhost:5000;
 }
 
 server {
-  listen 80;
-  listen [::]:80;
-  server_name demo.funkwhale.audio;
-  # useful for Let's Encrypt
-  location /.well-known/acme-challenge/ { allow all; }
-  location / { return 301 https://$host$request_uri; }
+    listen 80;
+    listen [::]:80;
+    # update this to match your instance name
+    server_name demo.funkwhale.audio;
+    # useful for Let's Encrypt
+    location /.well-known/acme-challenge/ { allow all; }
+    location / { return 301 https://$host$request_uri; }
 }
 
 server {
     listen      443 ssl http2;
     listen [::]:443 ssl http2;
+    # update this to match your instance name
     server_name demo.funkwhale.audio;
 
     # TLS
+    # Feel free to use your own configuration for SSL here or simply remove the
+    # lines and move the configuration to the previous server block if you
+    # don't want to run funkwhale behind https (this is not recommanded)
+    # have a look here for let's encrypt configuration:
+    # https://certbot.eff.org/all-instructions/#debian-9-stretch-nginx
     ssl_protocols TLSv1.2;
     ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
     ssl_prefer_server_ciphers on;
     ssl_session_cache shared:SSL:10m;
-    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
-    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
+    ssl_certificate     /etc/letsencrypt/live/demo.funkwhale.audio/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/demo.funkwhale.audio/privkey.pem;
     # HSTS
     add_header Strict-Transport-Security "max-age=31536000";
 
diff --git a/dev.yml b/dev.yml
index 78bf76fcd30f479ffbd552dc127a5262f48cdf58..c71298cfc8932c1b7874a906b582fe85a59ee4bd 100644
--- a/dev.yml
+++ b/dev.yml
@@ -33,7 +33,12 @@ services:
      - redis
     command: python manage.py celery worker
     environment:
-        - C_FORCE_ROOT=true
+      - "DJANGO_ALLOWED_HOSTS=localhost"
+      - "DJANGO_SETTINGS_MODULE=config.settings.local"
+      - "DJANGO_SECRET_KEY=dev"
+      - C_FORCE_ROOT=true
+      - "DATABASE_URL=postgresql://postgres@postgres/postgres"
+      - "CACHE_URL=redis://redis:6379/0"
     volumes:
       - ./api:/app
       - ./data/music:/music
@@ -46,12 +51,17 @@ services:
     volumes:
       - ./api:/app
       - ./data/music:/music
+    environment:
+      - "DJANGO_ALLOWED_HOSTS=localhost"
+      - "DJANGO_SETTINGS_MODULE=config.settings.local"
+      - "DJANGO_SECRET_KEY=dev"
+      - "DATABASE_URL=postgresql://postgres@postgres/postgres"
+      - "CACHE_URL=redis://redis:6379/0"
     ports:
       - "12081:12081"
     links:
       - postgres
       - redis
-      - celeryworker
 
   nginx:
     env_file: .env.dev
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 47bf0ed9cd4688db5806606d19555c7cfe7ab94a..0e6872f4b67b1b77fa07dcf4c23f2553172b01f5 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -21,9 +21,7 @@ Changelog
 * [feature] can now import artist and releases from youtube and musicbrainz.
   This requires a YouTube API key for the search
 * [breaking] we now check for user permission before serving audio files, which requires
-a specific configuration block in your reverse proxy configuration:
-
-.. code-block::
+  a specific configuration block in your reverse proxy configuration::
 
     location /_protected/media {
         internal;
diff --git a/docs/conf.py b/docs/conf.py
index 5a5effa3cf49ec1fdf384244df4abba5ed9b3763..3a0c8f6f19d8a31d5e1f9ccb48bfa98d9c00fbdb 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -17,10 +17,12 @@
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
+import os
+import sys
 
+sys.path.insert(0, os.path.abspath('../api'))
+
+import funkwhale_api  # NOQA
 
 # -- General configuration ------------------------------------------------
 
@@ -55,9 +57,11 @@ author = 'Eliot Berriot'
 # built documents.
 #
 # The short X.Y version.
-version = '0.1'
+# version = funkwhale_api.__version__
+# @TODO use real version here
+version = 'feature/22-debian-installation'
 # The full version, including alpha/beta/rc tags.
-release = '0.1'
+release = version
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -152,6 +156,3 @@ texinfo_documents = [
      author, 'funkwhale', 'One line description of project.',
      'Miscellaneous'),
 ]
-
-
-
diff --git a/docs/importing-music.rst b/docs/importing-music.rst
index 5fdff7e931c571b07632dfe2a659f239c928757f..3fa9e999008e36d9276b2c94137f109ee397c840 100644
--- a/docs/importing-music.rst
+++ b/docs/importing-music.rst
@@ -5,10 +5,18 @@ From music directory on the server
 ----------------------------------
 
 You can import music files in funkwhale assuming they are located on the server
-and readable by the funkwhale application.
+and readable by the funkwhale application. Your music files should contain at
+least an ``artist``, ``album`` and ``title`` tags.
 
-Assuming your music is located at ``/music`` and your music files contains at
-least an ``artist``, ``album`` and ``title`` tag, you can import those tracks as follows:
+You can import those tracks as follows, assuming they are located in
+``/srv/funkwhale/data/music``:
+
+.. code-block:: bash
+
+    python api/manage.py import_files "/srv/funkwhale/data/music/**/*.ogg" --recursive --noinput
+
+When you use docker, the ``/srv/funkwhale/data/music`` is mounted from the host
+to the ``/music`` directory on the container:
 
 .. code-block:: bash
 
@@ -17,6 +25,7 @@ least an ``artist``, ``album`` and ``title`` tag, you can import those tracks as
 For the best results, we recommand tagging your music collection through
 `Picard <http://picard.musicbrainz.org/>`_ in order to have the best quality metadata.
 
+
 .. note::
 
     This command is idempotent, meaning you can run it multiple times on the same
@@ -26,6 +35,18 @@ For the best results, we recommand tagging your music collection through
 
     At the moment, only OGG/Vorbis and MP3 files with ID3 tags are supported
 
+.. note::
+
+    The --recursive flag will work only on Python 3.5+, which is the default
+    version When using Docker or Debian 9. If you use an older version of Python,
+    remove the --recursive flag and use more explicit import patterns instead::
+
+        # this will only import ogg files at the second level
+        "/srv/funkwhale/data/music/*/*.ogg"
+        # this will only import ogg files in the fiven directory
+        "/srv/funkwhale/data/music/System-of-a-down/*.ogg"
+
+
 
 Getting demo tracks
 ^^^^^^^^^^^^^^^^^^^
@@ -34,10 +55,10 @@ If you do not have any music on your server but still want to test the import
 process, you can call the following methods do download a few albums licenced
 under creative commons (courtesy of Jamendo):
 
-.. code-block:: bash
+.. parsed-literal::
 
-    curl -L -o download-tracks.sh "https://code.eliotberriot.com/funkwhale/funkwhale/raw/master/demo/download-tracks.sh"
-    curl -L -o music.txt "https://code.eliotberriot.com/funkwhale/funkwhale/raw/master/demo/music.txt"
+    curl -L -o download-tracks.sh "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/demo/download-tracks.sh"
+    curl -L -o music.txt "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/demo/music.txt"
     chmod +x download-tracks.sh
     ./download-tracks.sh music.txt
 
diff --git a/docs/installation/debian.rst b/docs/installation/debian.rst
new file mode 100644
index 0000000000000000000000000000000000000000..86ccb4dd3ee6df1678f758d0e8e7834bb16a4e3b
--- /dev/null
+++ b/docs/installation/debian.rst
@@ -0,0 +1,280 @@
+Debian installation
+===================
+
+.. note::
+
+    this guide targets Debian 9, which is the latest debian, but should work
+    similarly on Debian 8.
+
+External dependencies
+---------------------
+
+The guides will focus on installing funkwhale-specific components and
+dependencies. However, funkwhale requires a
+:doc:`few external dependencies <./external_dependencies>` for which
+documentation is outside of this document scope.
+
+Install utilities
+-----------------
+
+You'll need a few utilities during this guide that are not always present by
+default on system. You can install them using:
+
+.. code-block:: shell
+
+    sudo apt-get update
+    sudo apt-get install curl python3-venv git unzip
+
+
+Layout
+-------
+
+All funkwhale-related files will be located under ``/srv/funkwhale`` apart
+from database files and a few configuration files. We will also have a
+dedicated ``funwhale`` user to launch the processes we need and own those files.
+
+You are free to use different values here, just remember to adapt those in the
+next steps.
+
+.. _create-funkwhale-user:
+
+Create the user and the directory:
+
+.. code-block:: shell
+
+    sudo adduser --system --home /srv/funkwhale funkwhale
+    cd /srv/funkwhale
+
+Log in as the newly created user from now on:
+
+.. code-block:: shell
+
+    sudo -u funkwhale -H bash
+
+Now let's setup our directory layout. Here is how it will look like::
+
+    .
+    ├── config      # config / environment files
+    ├── api         # api code of your instance
+    ├── data        # persistent data, such as music files
+    ├── front       # frontend files for the web user interface
+    └── virtualenv  # python dependencies for funkwhale
+
+Create the aforementionned directories:
+
+.. code-block:: shell
+
+    mkdir -p config api data/static data/media data/music front
+
+The ``virtualenv`` directory is a bit special and will be created separately.
+
+Download latest funkwhale release
+----------------------------------
+
+Funkwhale is splitted in two components:
+
+1. The API, which will handle music storage and user accounts
+2. The frontend, that will simply connect to the API to interact with its data
+
+Those components are packaged in subsequent releases, such as 0.1, 0.2, etc.
+You can browse the :doc:`changelog </changelog>` for a list of available releases
+and pick the one you want to install, usually the latest one should be okay.
+
+In this guide, we'll assume you want to install the latest version of funkwhale,
+which is |version|:
+
+First, we'll download the latest api release.
+
+.. parsed-literal::
+
+    curl -L -o "api-|version|.zip" "https://code.eliotberriot.com/funkwhale/funkwhale/-/jobs/artifacts/|version|/download?job=build_api"
+    unzip "api-|version|.zip" -d extracted
+    mv extracted/api api
+    rmdir extracted
+
+
+Then we'll download the frontend files:
+
+.. parsed-literal::
+
+    curl -L -o "front-|version|.zip" "https://code.eliotberriot.com/funkwhale/funkwhale/-/jobs/artifacts/|version|/download?job=build_front"
+    unzip "front-|version|.zip" -d extracted
+    mv extracted/front .
+    rmdir extracted
+
+You can leave the ZIP archives in the directory, this will help you know
+which version you've installed next time you want to upgrade your installation.
+
+System dependencies
+-------------------
+
+First, switch to the api directory:
+
+.. code-block:: shell
+
+    cd api
+
+A few OS packages are required in order to run funkwhale. The list is available
+in ``api/requirements.apt`` or by running
+``./install_os_dependencies.sh list``.
+
+.. note::
+
+    Ensure you are running the next commands as root or using sudo
+    (and not as the funkwhale) user.
+
+You can install those packages all at once:
+
+.. code-block:: shell
+
+    ./install_os_dependencies.sh install
+
+From now on you can switch back to the funkwhale user.
+
+Python dependencies
+--------------------
+
+Go back to the base directory:
+
+.. code-block:: shell
+
+    cd /srv/funkwhale
+
+To avoid collisions with other software on your system, Python dependencies
+will be installed in a dedicated
+`virtualenv <https://docs.python.org/3/library/venv.html>`_.
+
+First, create the virtualenv:
+
+.. code-block:: shell
+
+    python3 -m venv /srv/funkwhale/virtualenv
+
+This will result in a ``virtualenv`` directory being created in
+``/srv/funkwhale/virtualenv``.
+
+In the rest of this guide, we'll need to activate this environment to ensure
+dependencies are installed within it, and not directly on your host system.
+
+This is done with the following command:
+
+.. code-block:: shell
+
+    source /srv/funkwhale/virtualenv/bin/activate
+
+Finally, install the python dependencies:
+
+.. code-block:: shell
+
+    pip install wheel
+    pip install -r api/requirements.txt
+
+.. important::
+
+    further commands involving python should always be run after you activated
+    the virtualenv, as described earlier, otherwise those commands will raise
+    errors
+
+
+Environment file
+----------------
+
+You can now start to configure funkwhale. The main way to achieve that is by
+adding an environment file that will host settings that are relevant to your
+installation.
+
+Download the sample environment file:
+
+.. parsed-literal::
+
+    curl -L -o config/.env "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/env.prod.sample"
+
+You can then edit it: the file is heavily commented, and the most relevant
+configuration options are mentionned at the top of the file.
+
+Especially, populate the ``DATABASE_URL`` and ``CACHE_URL`` values based on
+how you configured your PostgreSQL and Redis servers in
+:doc:`external dependencies <./external_dependencies>`.
+
+
+When you want to run command on the API server, such as to create the
+database or compile static files, you have to ensure you source
+the environment variables.
+
+This can be done like this::
+
+    export $(cat config/.env | grep -v ^# | xargs)
+
+The easier thing to do is to store this in a script::
+
+    cat > /srv/funkwhale/load_env <<'EOL'
+    #!/bin/bash
+    export $(cat /srv/funkwhale/config/.env | grep -v ^# | xargs)
+    EOL
+    chmod +x /srv/funkwhale/load_env
+
+You should now be able to run the following to populate your environment
+variables easily:
+
+.. code-block:: shell
+
+    source /srv/funkwhale/load_env
+
+.. note::
+
+    Remember to source ``load_env`` whenever you edit your .env file.
+
+Database setup
+---------------
+
+You should now be able to import the initial database structure:
+
+.. code-block:: shell
+
+    python api/manage.py migrate
+
+This will create the required tables and rows.
+
+.. note::
+
+    You can safely execute this command any time you want, this will only
+    run unapplied migrations.
+
+
+Create an admin account
+-----------------------
+
+You can then create your first user account:
+
+.. code-block:: shell
+
+    python api/manage.py createsuperuser
+
+If you ever want to change a user's password from the command line, just run:
+
+.. code-block:: shell
+
+    python api/manage.py changepassword <user>
+
+Collect static files
+--------------------
+
+Static files are the static assets used by the API server (icon PNGs, CSS, etc.).
+We need to collect them explicitely, so they can be served by the webserver:
+
+.. code-block:: shell
+
+    python api/manage.py collectstatic
+
+This should populate the directory you choose for the ``STATIC_ROOT`` variable
+in your ``.env`` file.
+
+Systemd unit file
+------------------
+
+See :doc:`./systemd`.
+
+Reverse proxy setup
+--------------------
+
+See :ref:`reverse-proxy <reverse-proxy-setup>`.
diff --git a/docs/installation/docker.rst b/docs/installation/docker.rst
index 76958fb0bfeb04be5469fde0d73130ece09e9cee..34e8187c58dc2e104392badf862dec1dcadcad43 100644
--- a/docs/installation/docker.rst
+++ b/docs/installation/docker.rst
@@ -7,17 +7,17 @@ First, ensure you have `Docker <https://docs.docker.com/engine/installation/>`_
 
 Download the sample docker-compose file:
 
-.. code-block:: bash
+.. parsed-literal::
 
     mkdir -p /srv/funkwhale
     cd /srv/funkwhale
-    curl -L -o docker-compose.yml "https://code.eliotberriot.com/funkwhale/funkwhale/raw/master/deploy/docker-compose.yml"
+    curl -L -o docker-compose.yml "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/docker-compose.yml"
 
 Create your env file:
 
-.. code-block:: bash
+.. parsed-literal::
 
-    curl -L -o .env "https://code.eliotberriot.com/funkwhale/funkwhale/raw/master/deploy/env.prod.sample"
+    curl -L -o .env "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/env.prod.sample"
 
 Ensure to edit it to match your needs (this file is heavily commented)
 
diff --git a/docs/installation/external_dependencies.rst b/docs/installation/external_dependencies.rst
new file mode 100644
index 0000000000000000000000000000000000000000..fa0908545611d67b4321bd19dbda01bb060f0dff
--- /dev/null
+++ b/docs/installation/external_dependencies.rst
@@ -0,0 +1,62 @@
+External dependencies
+=====================
+
+
+.. note::
+
+    Those dependencies are handled automatically if you are
+    :doc:`deploying using docker <./docker>`
+
+Database setup (PostgreSQL)
+---------------------------
+
+Funkwhale requires a PostgreSQL database to work properly. Please refer
+to the `PostgreSQL documentation <https://www.postgresql.org/download/>`_
+for installation instructions specific to your os.
+
+On debian-like systems, you would install the database server like this:
+
+.. code-block:: shell
+
+    sudo apt-get install postgresql
+
+The remaining steps are heavily inspired from `this Digital Ocean guide <https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04>`_.
+
+Open a database shell:
+
+.. code-block:: shell
+
+    sudo -u postgres psql
+
+Create the project database and user:
+
+.. code-block:: shell
+
+    CREATE DATABASE funkwhale;
+    CREATE USER funkwhale;
+    GRANT ALL PRIVILEGES ON DATABASE funkwhale TO funkwhale;
+
+Assuming you already have :ref:`created your funkwhale user <create-funkwhale-user>`,
+you should now be able to open a postgresql shell:
+
+.. code-block:: shell
+
+    sudo -u funkwhale -H psql
+
+Cache setup (Redis)
+-------------------
+
+Funkwhale also requires a cache server:
+
+- To make the whole system faster, by caching network requests or database
+  queries
+- To handle asynchronous tasks such as music import
+
+On debian-like distributions, a redis package is available, and you can
+install it:
+
+.. code-block:: shell
+
+    sudo apt-get install redis-server
+
+This should be enough to have your redis server set up.
diff --git a/docs/installation/index.rst b/docs/installation/index.rst
index 1544dfbf0986a017c54e21e1de7031cf5390f6d3..218049dd10ab5b2df7c27d4e2dd9658f52e22ec0 100644
--- a/docs/installation/index.rst
+++ b/docs/installation/index.rst
@@ -18,7 +18,10 @@ Available installation methods
 .. toctree::
    :maxdepth: 1
 
+   external_dependencies
+   debian
    docker
+   systemd
 
 
 .. _frontend-setup:
@@ -33,10 +36,10 @@ Frontend setup
 
 Files for the web frontend are purely static and can simply be downloaded, unzipped and served from any webserver:
 
-.. code-block:: bash
+.. parsed-literal::
 
     cd /srv/funkwhale
-    curl -L -o front.zip "https://code.eliotberriot.com/funkwhale/funkwhale/builds/artifacts/master/download?job=build_front"
+    curl -L -o front.zip "https://code.eliotberriot.com/funkwhale/funkwhale/builds/artifacts/|version|/download?job=build_front"
     unzip front.zip
 
 .. _reverse-proxy-setup:
@@ -58,8 +61,8 @@ Ensure you have a recent version of nginx on your server. On debian-like system,
 
 Then, download our sample virtualhost file:
 
-.. code-block:: bash
+.. parsed-literal::
 
-    curl -L -o /etc/nginx/sites-enabled/funkwhale.conf "https://code.eliotberriot.com/funkwhale/funkwhale/raw/master/deploy/nginx.conf"
+    curl -L -o /etc/nginx/sites-enabled/funkwhale.conf "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/nginx.conf"
 
 Ensure static assets and proxy pass match your configuration, and check the configuration is valid with ``nginx -t``. If everything is fine, you can restart your nginx server with ``service nginx restart``.
diff --git a/docs/installation/systemd.rst b/docs/installation/systemd.rst
new file mode 100644
index 0000000000000000000000000000000000000000..67af98432641dc837c8313ca1578cc3974dee70f
--- /dev/null
+++ b/docs/installation/systemd.rst
@@ -0,0 +1,42 @@
+Systemd configuration
+----------------------
+
+Systemd offers a convenient way to manage your funkwhale instance if you're
+not using docker.
+
+We'll see how to setup systemd to proprely start a funkwhale instance.
+
+First, download the sample unitfiles:
+
+.. parsed-literal::
+
+    curl -L -o "/etc/systemd/system/funkwhale.target" "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/funkwhale.target"
+    curl -L -o "/etc/systemd/system/funkwhale-server.service" "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-server.service"
+    curl -L -o "/etc/systemd/system/funkwhale-worker.service" "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/funkwhale-worker.service"
+
+This will download three unitfiles:
+
+- ``funkwhale-server.service`` to launch the funkwhale web server
+- ``funkwhale-worker.service`` to launch the funkwhale task worker
+- ``funkwhale.target`` to easily stop and start all of the services at once
+
+You can of course review and edit them to suit your deployment scenario
+if needed, but the defaults should be fine.
+
+Once the files are downloaded, reload systemd:
+
+.. code-block:: shell
+
+    systemctl daemon-reload
+
+And start the services:
+
+.. code-block:: shell
+
+    systemctl start funkwhale.target
+
+You can check the statuses of all processes like this:
+
+.. code-block:: shell
+
+    systemctl status funkwhale-\*
diff --git a/front/src/audio/queue.js b/front/src/audio/queue.js
index 77fe6bf2bc89bf0f9fc114b52f7512f9ea19ef94..bde0bf863fc4b04f5fd73102659334c633683f07 100644
--- a/front/src/audio/queue.js
+++ b/front/src/audio/queue.js
@@ -116,7 +116,7 @@ class Queue {
     }
   }
 
-  append (track, index) {
+  append (track, index, skipPlay) {
     this.previousQueue = null
     index = index || this.tracks.length
     if (index > this.tracks.length - 1) {
@@ -126,20 +126,32 @@ class Queue {
       // we insert the track at given position
       this.tracks.splice(index, 0, track)
     }
-    if (this.ended) {
-      logger.default.debug('Playing appended track')
-      this.play(this.currentIndex + 1)
+    if (!skipPlay) {
+      this.resumeQueue()
     }
     this.cache()
   }
 
   appendMany (tracks, index) {
+    logger.default.info('Appending many tracks to the queue', tracks.map(e => { return e.title }))
     let self = this
-    index = index || this.tracks.length - 1
+    if (this.tracks.length === 0) {
+      index = 0
+    } else {
+      index = index || this.tracks.length
+    }
+    console.log('INDEEEEEX', index)
     tracks.forEach((t) => {
-      self.append(t, index)
+      self.append(t, index, true)
       index += 1
     })
+    this.resumeQueue()
+  }
+
+  resumeQueue () {
+    if (this.ended | this.errored) {
+      this.next()
+    }
   }
 
   populateFromRadio () {
@@ -185,15 +197,24 @@ class Queue {
   }
 
   stop () {
-    this.audio.pause()
-    this.audio.destroyed()
+    if (this.audio.pause) {
+      this.audio.pause()
+    }
+    if (this.audio.destroyed) {
+      this.audio.destroyed()
+    }
   }
   play (index) {
     let self = this
     let currentIndex = index
     let currentTrack = this.tracks[index]
+
+    if (this.audio.destroyed) {
+      logger.default.debug('Destroying previous audio...', index - 1)
+      this.audio.destroyed()
+    }
+
     if (!currentTrack) {
-      logger.default.debug('No track at index', index)
       return
     }
 
@@ -201,12 +222,13 @@ class Queue {
     this.currentTrack = currentTrack
 
     this.ended = false
+    this.errored = false
     let file = this.currentTrack.files[0]
     if (!file) {
+      this.errored = true
       return this.next()
     }
     let path = backend.absoluteUrl(file.path)
-
     if (auth.user.authenticated) {
       // we need to send the token directly in url
       // so authentication can be checked by the backend
@@ -215,10 +237,6 @@ class Queue {
       path = url.updateQueryString(path, 'jwt', auth.getAuthToken())
     }
 
-    if (this.audio.destroyed) {
-      logger.default.debug('Destroying previous audio...', index - 1)
-      this.audio.destroyed()
-    }
     let audio = new Audio(path, {
       preload: true,
       autoplay: true,
@@ -271,6 +289,7 @@ class Queue {
 
   next () {
     if (this.currentIndex < this.tracks.length - 1) {
+      logger.default.debug('Playing next track')
       this.play(this.currentIndex + 1)
     }
   }
diff --git a/front/src/components/audio/track/Table.vue b/front/src/components/audio/track/Table.vue
index 055c986d2fdf416d01215fa328ba59bea3a34c07..efb98e382804ecb2623d4f96f5e6b9935559052b 100644
--- a/front/src/components/audio/track/Table.vue
+++ b/front/src/components/audio/track/Table.vue
@@ -59,8 +59,8 @@
                 </div>
                 <pre>
 export PRIVATE_TOKEN="{{ auth.getAuthToken ()}}"
-<template v-for="track in tracks">
-curl -G -o "{{ track.files[0].filename }}" <template v-if="auth.user.authenticated">--header "Authorization: JWT $PRIVATE_TOKEN"</template> "{{ backend.absoluteUrl(track.files[0].path) }}"</template>
+<template v-for="track in tracks"><template v-if="track.files.length > 0">
+curl -G -o "{{ track.files[0].filename }}" <template v-if="auth.user.authenticated">--header "Authorization: JWT $PRIVATE_TOKEN"</template> "{{ backend.absoluteUrl(track.files[0].path) }}"</template></template>
 </pre>
               </div>
             </div>
diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue
index 07727a0595efcc550f61ec0738b9d7070d0b146c..2f0fb0a9236197d725191d4d1106037f855d3955 100644
--- a/front/src/components/library/Artists.vue
+++ b/front/src/components/library/Artists.vue
@@ -30,6 +30,7 @@
 <script>
 
 import config from '@/config'
+import backend from '@/audio/backend'
 import logger from '@/logging'
 import ArtistCard from '@/components/audio/artist/Card'
 import Pagination from '@/components/Pagination'
@@ -66,6 +67,13 @@ export default {
       logger.default.debug('Fetching artists')
       this.$http.get(url, {params: params}).then((response) => {
         self.result = response.data
+        self.result.results.map((artist) => {
+          var albums = JSON.parse(JSON.stringify(artist.albums)).map((album) => {
+            return backend.Album.clean(album)
+          })
+          artist.albums = albums
+          return artist
+        })
         self.isLoading = false
       })
     },
diff --git a/front/src/components/library/import/BatchDetail.vue b/front/src/components/library/import/BatchDetail.vue
index 57560fc04e5779e55d20189a5623ab619b6953a8..726ec9c3e29eed7e6e3aa8433e7140d3b2a40cc4 100644
--- a/front/src/components/library/import/BatchDetail.vue
+++ b/front/src/components/library/import/BatchDetail.vue
@@ -23,6 +23,7 @@
             <th>Recording MusicBrainz ID</th>
             <th>Source</th>
             <th>Status</th>
+            <th>Track</th>
           </tr>
         </thead>
         <tbody>
@@ -38,6 +39,9 @@
               <span
                 :class="['ui', {'yellow': job.status === 'pending'}, {'green': job.status === 'finished'}, 'label']">{{ job.status }}</span>
             </td>
+            <td>
+              <router-link v-if="job.track_file" :to="{name: 'library.tracks.detail', params: {id: job.track_file.track }}">{{ job.track_file.track }}</router-link>
+            </td>
           </tr>
         </tbody>
       </table>