diff --git a/api/adminmonitor/admin_monitor.go b/api/adminmonitor/admin_monitor.go
new file mode 100644
index 000000000..e22ae2c5b
--- /dev/null
+++ b/api/adminmonitor/admin_monitor.go
@@ -0,0 +1,69 @@
+package adminmonitor
+
+import (
+	"context"
+	"log"
+	"time"
+
+	portainer "github.com/portainer/portainer/api"
+)
+
+var logFatalf = log.Fatalf
+
+type Monitor struct {
+	timeout          time.Duration
+	datastore        portainer.DataStore
+	shutdownCtx      context.Context
+	cancellationFunc context.CancelFunc
+}
+
+// New creates a monitor that when started will wait for the timeout duration and then shutdown the application unless it has been initialized.
+func New(timeout time.Duration, datastore portainer.DataStore, shutdownCtx context.Context) *Monitor {
+	return &Monitor{
+		timeout:     timeout,
+		datastore:   datastore,
+		shutdownCtx: shutdownCtx,
+	}
+}
+
+// Starts starts the monitor. Active monitor could be stopped or shuttted down by cancelling the shutdown context.
+func (m *Monitor) Start() {
+	cancellationCtx, cancellationFunc := context.WithCancel(context.Background())
+	m.cancellationFunc = cancellationFunc
+
+	go func() {
+		log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
+		select {
+		case <-time.After(m.timeout):
+			initialized, err := m.WasInitialized()
+			if err != nil {
+				logFatalf("%s", err)
+			}
+			if !initialized {
+				logFatalf("[FATAL] [internal,init] No administrator account was created in %f mins. Shutting down the Portainer instance for security reasons", m.timeout.Minutes())
+			}
+		case <-cancellationCtx.Done():
+			log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
+		case <-m.shutdownCtx.Done():
+			log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
+		}
+	}()
+}
+
+// Stop stops monitor. Safe to call even if monitor wasn't started.
+func (m *Monitor) Stop() {
+	if m.cancellationFunc == nil {
+		return
+	}
+	m.cancellationFunc()
+	m.cancellationFunc = nil
+}
+
+// WasInitialized is a system initialization check
+func (m *Monitor) WasInitialized() (bool, error) {
+	users, err := m.datastore.User().UsersByRole(portainer.AdministratorRole)
+	if err != nil {
+		return false, err
+	}
+	return len(users) > 0, nil
+}
diff --git a/api/adminmonitor/admin_monitor_test.go b/api/adminmonitor/admin_monitor_test.go
new file mode 100644
index 000000000..1df983276
--- /dev/null
+++ b/api/adminmonitor/admin_monitor_test.go
@@ -0,0 +1,50 @@
+package adminmonitor
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	portainer "github.com/portainer/portainer/api"
+	i "github.com/portainer/portainer/api/internal/testhelpers"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_stopWithoutStarting(t *testing.T) {
+	monitor := New(1*time.Minute, nil, nil)
+	monitor.Stop()
+}
+
+func Test_stopCouldBeCalledMultipleTimes(t *testing.T) {
+	monitor := New(1*time.Minute, nil, nil)
+	monitor.Stop()
+	monitor.Stop()
+}
+
+func Test_canStopStartedMonitor(t *testing.T) {
+	monitor := New(1*time.Minute, nil, context.Background())
+	monitor.Start()
+	assert.NotNil(t, monitor.cancellationFunc, "cancellation function is missing in started monitor")
+
+	monitor.Stop()
+	assert.Nil(t, monitor.cancellationFunc, "cancellation function should absent in stopped monitor")
+}
+
+func Test_start_shouldFatalAfterTimeout_ifNotInitialized(t *testing.T) {
+	timeout := 10 * time.Millisecond
+
+	datastore := i.NewDatastore(i.WithUsers([]portainer.User{}))
+
+	var fataled bool
+	origLogFatalf := logFatalf
+	logFatalf = func(s string, v ...interface{}) { fataled = true }
+	defer func() {
+		logFatalf = origLogFatalf
+	}()
+
+	monitor := New(timeout, datastore, context.Background())
+	monitor.Start()
+	<-time.After(2 * timeout)
+
+	assert.True(t, fataled, "monitor should been timeout and fatal")
+}
diff --git a/api/archive/targz.go b/api/archive/targz.go
new file mode 100644
index 000000000..757854a23
--- /dev/null
+++ b/api/archive/targz.go
@@ -0,0 +1,119 @@
+package archive
+
+import (
+	"archive/tar"
+	"compress/gzip"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// TarGzDir creates a tar.gz archive and returns it's path.
+// abosolutePath should be an absolute path to a directory.
+// Archive name will be <directoryName>.tar.gz and will be placed next to the directory.
+func TarGzDir(absolutePath string) (string, error) {
+	targzPath := filepath.Join(absolutePath, fmt.Sprintf("%s.tar.gz", filepath.Base(absolutePath)))
+	outFile, err := os.Create(targzPath)
+	if err != nil {
+		return "", err
+	}
+	defer outFile.Close()
+
+	zipWriter := gzip.NewWriter(outFile)
+	defer zipWriter.Close()
+	tarWriter := tar.NewWriter(zipWriter)
+	defer tarWriter.Close()
+
+	err = filepath.Walk(absolutePath, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if path == targzPath {
+			return nil // skip archive file
+		}
+
+		pathInArchive := filepath.Clean(strings.TrimPrefix(path, absolutePath))
+		if pathInArchive == "" {
+			return nil // skip root dir
+		}
+
+		return addToArchive(tarWriter, pathInArchive, path, info)
+	})
+
+	return targzPath, err
+}
+
+func addToArchive(tarWriter *tar.Writer, pathInArchive string, path string, info os.FileInfo) error {
+	header, err := tar.FileInfoHeader(info, info.Name())
+	if err != nil {
+		return err
+	}
+
+	header.Name = pathInArchive // use relative paths in archive
+
+	err = tarWriter.WriteHeader(header)
+	if err != nil {
+		return err
+	}
+
+	if info.IsDir() {
+		return nil
+	}
+
+	file, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	_, err = io.Copy(tarWriter, file)
+	return err
+}
+
+// ExtractTarGz reads a .tar.gz archive from the reader and extracts it into outputDirPath directory
+func ExtractTarGz(r io.Reader, outputDirPath string) error {
+	zipReader, err := gzip.NewReader(r)
+	if err != nil {
+		return err
+	}
+	defer zipReader.Close()
+
+	tarReader := tar.NewReader(zipReader)
+
+	for {
+		header, err := tarReader.Next()
+
+		if err == io.EOF {
+			break
+		}
+
+		if err != nil {
+			return err
+		}
+
+		switch header.Typeflag {
+		case tar.TypeDir:
+			// skip, dir will be created with a file
+		case tar.TypeReg:
+			p := filepath.Clean(filepath.Join(outputDirPath, header.Name))
+			if err := os.MkdirAll(filepath.Dir(p), 0744); err != nil {
+				return fmt.Errorf("Failed to extract dir %s", filepath.Dir(p))
+			}
+			outFile, err := os.Create(p)
+			if err != nil {
+				return fmt.Errorf("Failed to create file %s", header.Name)
+			}
+			if _, err := io.Copy(outFile, tarReader); err != nil {
+				return fmt.Errorf("Failed to extract file %s", header.Name)
+			}
+			outFile.Close()
+		default:
+			return fmt.Errorf("Tar: uknown type: %v in %s",
+				header.Typeflag,
+				header.Name)
+		}
+	}
+
+	return nil
+}
diff --git a/api/archive/targz_test.go b/api/archive/targz_test.go
new file mode 100644
index 000000000..ed8a67543
--- /dev/null
+++ b/api/archive/targz_test.go
@@ -0,0 +1,99 @@
+package archive
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"testing"
+
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/stretchr/testify/assert"
+)
+
+func listFiles(dir string) []string {
+	items := make([]string, 0)
+	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if path == dir {
+			return nil
+		}
+		items = append(items, path)
+		return nil
+	})
+
+	return items
+}
+
+func Test_shouldCreateArhive(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	content := []byte("content")
+	ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
+	os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
+	ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
+	ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
+
+	gzPath, err := TarGzDir(tmpdir)
+	assert.Nil(t, err)
+	assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
+
+	extractionDir, _ := ioutils.TempDir("", "extract")
+	defer os.RemoveAll(extractionDir)
+
+	cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
+	err = cmd.Run()
+	if err != nil {
+		t.Fatal("Failed to extract archive: ", err)
+	}
+	extractedFiles := listFiles(extractionDir)
+
+	wasExtracted := func(p string) {
+		fullpath := path.Join(extractionDir, p)
+		assert.Contains(t, extractedFiles, fullpath)
+		copyContent, _ := ioutil.ReadFile(fullpath)
+		assert.Equal(t, content, copyContent)
+	}
+
+	wasExtracted("outer")
+	wasExtracted("dir/inner")
+	wasExtracted("dir/.dotfile")
+}
+
+func Test_shouldCreateArhiveXXXXX(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	content := []byte("content")
+	ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
+	os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
+	ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
+	ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
+
+	gzPath, err := TarGzDir(tmpdir)
+	assert.Nil(t, err)
+	assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
+
+	extractionDir, _ := ioutils.TempDir("", "extract")
+	defer os.RemoveAll(extractionDir)
+
+	r, _ := os.Open(gzPath)
+	ExtractTarGz(r, extractionDir)
+	if err != nil {
+		t.Fatal("Failed to extract archive: ", err)
+	}
+	extractedFiles := listFiles(extractionDir)
+
+	wasExtracted := func(p string) {
+		fullpath := path.Join(extractionDir, p)
+		assert.Contains(t, extractedFiles, fullpath)
+		copyContent, _ := ioutil.ReadFile(fullpath)
+		assert.Equal(t, content, copyContent)
+	}
+
+	wasExtracted("outer")
+	wasExtracted("dir/inner")
+	wasExtracted("dir/.dotfile")
+}
diff --git a/api/backup/backup.go b/api/backup/backup.go
new file mode 100644
index 000000000..3da032c2c
--- /dev/null
+++ b/api/backup/backup.go
@@ -0,0 +1,83 @@
+package backup
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/pkg/errors"
+	portainer "github.com/portainer/portainer/api"
+	"github.com/portainer/portainer/api/archive"
+	"github.com/portainer/portainer/api/crypto"
+	"github.com/portainer/portainer/api/http/offlinegate"
+)
+
+const rwxr__r__ os.FileMode = 0744
+
+var filesToBackup = []string{"compose", "config.json", "custom_templates", "edge_jobs", "edge_stacks", "extensions", "portainer.key", "portainer.pub", "tls"}
+
+// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
+func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore portainer.DataStore, filestorePath string) (string, error) {
+	unlock := gate.Lock()
+	defer unlock()
+
+	backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
+	if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
+		return "", errors.Wrap(err, "Failed to create backup dir")
+	}
+
+	if err := backupDb(backupDirPath, datastore); err != nil {
+		return "", errors.Wrap(err, "Failed to backup database")
+	}
+
+	for _, filename := range filesToBackup {
+		err := copyPath(filepath.Join(filestorePath, filename), backupDirPath)
+		if err != nil {
+			return "", errors.Wrap(err, "Failed to create backup file")
+		}
+	}
+
+	archivePath, err := archive.TarGzDir(backupDirPath)
+	if err != nil {
+		return "", errors.Wrap(err, "Failed to make an archive")
+	}
+
+	if password != "" {
+		archivePath, err = encrypt(archivePath, password)
+		if err != nil {
+			return "", errors.Wrap(err, "Failed to encrypt backup with the password")
+		}
+	}
+
+	return archivePath, nil
+}
+
+func backupDb(backupDirPath string, datastore portainer.DataStore) error {
+	backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
+	if err != nil {
+		return err
+	}
+	if err = datastore.BackupTo(backupWriter); err != nil {
+		return err
+	}
+	return backupWriter.Close()
+}
+
+func encrypt(path string, passphrase string) (string, error) {
+	in, err := os.Open(path)
+	if err != nil {
+		return "", err
+	}
+	defer in.Close()
+
+	outFileName := fmt.Sprintf("%s.encrypted", path)
+	out, err := os.Create(outFileName)
+	if err != nil {
+		return "", err
+	}
+
+	err = crypto.AesEncrypt(in, out, []byte(passphrase))
+
+	return outFileName, err
+}
diff --git a/api/backup/copy.go b/api/backup/copy.go
new file mode 100644
index 000000000..6aaefd54c
--- /dev/null
+++ b/api/backup/copy.go
@@ -0,0 +1,68 @@
+package backup
+
+import (
+	"errors"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+func copyPath(path string, toDir string) error {
+	info, err := os.Stat(path)
+	if err != nil && errors.Is(err, os.ErrNotExist) {
+		// skip copy if file does not exist
+		return nil
+	}
+
+	if !info.IsDir() {
+		destination := filepath.Join(toDir, info.Name())
+		return copyFile(path, destination)
+	}
+
+	return copyDir(path, toDir)
+}
+
+func copyDir(fromDir, toDir string) error {
+	cleanedSourcePath := filepath.Clean(fromDir)
+	parentDirectory := filepath.Dir(cleanedSourcePath)
+	err := filepath.Walk(cleanedSourcePath, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		destination := filepath.Join(toDir, strings.TrimPrefix(path, parentDirectory))
+		if info.IsDir() {
+			return nil // skip directory creations
+		}
+
+		if info.Mode()&os.ModeSymlink != 0 { // entry is a symlink
+			return nil // don't copy symlinks
+		}
+
+		return copyFile(path, destination)
+	})
+
+	return err
+}
+
+// copies regular a file from src to dst
+func copyFile(src, dst string) error {
+	from, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer from.Close()
+
+	// has to include 'execute' bit, otherwise fails. MkdirAll follows `mkdir -m` restrictions
+	if err := os.MkdirAll(filepath.Dir(dst), 0744); err != nil {
+		return err
+	}
+	to, err := os.Create(dst)
+	if err != nil {
+		return err
+	}
+	defer to.Close()
+
+	_, err = io.Copy(to, from)
+	return err
+}
diff --git a/api/backup/copy_test.go b/api/backup/copy_test.go
new file mode 100644
index 000000000..b9ceaeaab
--- /dev/null
+++ b/api/backup/copy_test.go
@@ -0,0 +1,105 @@
+package backup
+
+import (
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"testing"
+
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/stretchr/testify/assert"
+)
+
+func listFiles(dir string) []string {
+	items := make([]string, 0)
+	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if path == dir {
+			return nil
+		}
+		items = append(items, path)
+		return nil
+	})
+
+	return items
+}
+
+func contains(t *testing.T, list []string, path string) {
+	assert.Contains(t, list, path)
+	copyContent, _ := ioutil.ReadFile(path)
+	assert.Equal(t, "content\n", string(copyContent))
+}
+
+func Test_copyFile_returnsError_whenSourceDoesNotExist(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	err := copyFile("does-not-exist", tmpdir)
+	assert.NotNil(t, err)
+}
+
+func Test_copyFile_shouldMakeAbackup(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	content := []byte("content")
+	ioutil.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
+
+	err := copyFile(path.Join(tmpdir, "origin"), path.Join(tmpdir, "copy"))
+	assert.Nil(t, err)
+
+	copyContent, _ := ioutil.ReadFile(path.Join(tmpdir, "copy"))
+	assert.Equal(t, content, copyContent)
+}
+
+func Test_copyDir_shouldCopyAllFilesAndDirectories(t *testing.T) {
+	destination, _ := ioutils.TempDir("", "destination")
+	defer os.RemoveAll(destination)
+	err := copyDir("./test_assets/copy_test", destination)
+	assert.Nil(t, err)
+
+	createdFiles := listFiles(destination)
+
+	contains(t, createdFiles, filepath.Join(destination, "copy_test", "outer"))
+	contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
+	contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", "inner"))
+}
+
+func Test_backupPath_shouldSkipWhenNotExist(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	err := copyPath("does-not-exists", tmpdir)
+	assert.Nil(t, err)
+
+	assert.Empty(t, listFiles(tmpdir))
+}
+
+func Test_backupPath_shouldCopyFile(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	content := []byte("content")
+	ioutil.WriteFile(path.Join(tmpdir, "file"), content, 0600)
+
+	os.MkdirAll(path.Join(tmpdir, "backup"), 0700)
+	err := copyPath(path.Join(tmpdir, "file"), path.Join(tmpdir, "backup"))
+	assert.Nil(t, err)
+
+	copyContent, err := ioutil.ReadFile(path.Join(tmpdir, "backup", "file"))
+	assert.Nil(t, err)
+	assert.Equal(t, content, copyContent)
+}
+
+func Test_backupPath_shouldCopyDir(t *testing.T) {
+	destination, _ := ioutils.TempDir("", "destination")
+	defer os.RemoveAll(destination)
+	err := copyPath("./test_assets/copy_test", destination)
+	assert.Nil(t, err)
+
+	createdFiles := listFiles(destination)
+
+	contains(t, createdFiles, filepath.Join(destination, "copy_test", "outer"))
+	contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
+	contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", "inner"))
+}
diff --git a/api/backup/restore.go b/api/backup/restore.go
new file mode 100644
index 000000000..b0d7acee2
--- /dev/null
+++ b/api/backup/restore.go
@@ -0,0 +1,68 @@
+package backup
+
+import (
+	"context"
+	"io"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/pkg/errors"
+	portainer "github.com/portainer/portainer/api"
+	"github.com/portainer/portainer/api/archive"
+	"github.com/portainer/portainer/api/crypto"
+	"github.com/portainer/portainer/api/http/offlinegate"
+)
+
+var filesToRestore = append(filesToBackup, "portainer.db")
+
+// Restores system state from backup archive, will trigger system shutdown, when finished.
+func RestoreArchive(archive io.Reader, password string, filestorePath string, gate *offlinegate.OfflineGate, datastore portainer.DataStore, shutdownTrigger context.CancelFunc) error {
+	var err error
+	if password != "" {
+		archive, err = decrypt(archive, password)
+		if err != nil {
+			return errors.Wrap(err, "failed to decrypt the archive")
+		}
+	}
+
+	restorePath := filepath.Join(filestorePath, "restore", time.Now().Format("20060102150405"))
+	defer os.RemoveAll(filepath.Dir(restorePath))
+
+	err = extractArchive(archive, restorePath)
+	if err != nil {
+		return errors.Wrap(err, "cannot extract files from the archive. Please ensure the password is correct and try again")
+	}
+
+	unlock := gate.Lock()
+	defer unlock()
+
+	if err = datastore.Close(); err != nil {
+		return errors.Wrap(err, "Failed to stop db")
+	}
+
+	if err = restoreFiles(restorePath, filestorePath); err != nil {
+		return errors.Wrap(err, "failed to restore the system state")
+	}
+
+	shutdownTrigger()
+	return nil
+}
+
+func decrypt(r io.Reader, password string) (io.Reader, error) {
+	return crypto.AesDecrypt(r, []byte(password))
+}
+
+func extractArchive(r io.Reader, destinationDirPath string) error {
+	return archive.ExtractTarGz(r, destinationDirPath)
+}
+
+func restoreFiles(srcDir string, destinationDir string) error {
+	for _, filename := range filesToRestore {
+		err := copyPath(filepath.Join(srcDir, filename), destinationDir)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/api/backup/test_assets/copy_test/dir/.dotfile b/api/backup/test_assets/copy_test/dir/.dotfile
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/backup/test_assets/copy_test/dir/.dotfile
@@ -0,0 +1 @@
+content
diff --git a/api/backup/test_assets/copy_test/dir/inner b/api/backup/test_assets/copy_test/dir/inner
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/backup/test_assets/copy_test/dir/inner
@@ -0,0 +1 @@
+content
diff --git a/api/backup/test_assets/copy_test/outer b/api/backup/test_assets/copy_test/outer
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/backup/test_assets/copy_test/outer
@@ -0,0 +1 @@
+content
diff --git a/api/bolt/customtemplate/customtemplate.go b/api/bolt/customtemplate/customtemplate.go
index 316af170e..f48dc882f 100644
--- a/api/bolt/customtemplate/customtemplate.go
+++ b/api/bolt/customtemplate/customtemplate.go
@@ -2,7 +2,7 @@ package customtemplate
 
 import (
 	"github.com/boltdb/bolt"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 )
 
@@ -13,18 +13,18 @@ const (
 
 // Service represents a service for managing custom template data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
 	var customTemplates = make([]portainer.CustomTemplate, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -56,7 +56,7 @@ func (service *Service) CustomTemplate(ID portainer.CustomTemplateID) (*portaine
 	var customTemplate portainer.CustomTemplate
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &customTemplate)
+	err := internal.GetObject(service.connection, BucketName, identifier, &customTemplate)
 	if err != nil {
 		return nil, err
 	}
@@ -67,18 +67,18 @@ func (service *Service) CustomTemplate(ID portainer.CustomTemplateID) (*portaine
 // UpdateCustomTemplate updates an custom template.
 func (service *Service) UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, customTemplate)
+	return internal.UpdateObject(service.connection, BucketName, identifier, customTemplate)
 }
 
 // DeleteCustomTemplate deletes an custom template.
 func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // CreateCustomTemplate assign an ID to a new custom template and saves it.
 func (service *Service) CreateCustomTemplate(customTemplate *portainer.CustomTemplate) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		data, err := internal.MarshalObject(customTemplate)
@@ -92,5 +92,5 @@ func (service *Service) CreateCustomTemplate(customTemplate *portainer.CustomTem
 
 // GetNextIdentifier returns the next identifier for a custom template.
 func (service *Service) GetNextIdentifier() int {
-	return internal.GetNextIdentifier(service.db, BucketName)
+	return internal.GetNextIdentifier(service.connection, BucketName)
 }
diff --git a/api/bolt/datastore.go b/api/bolt/datastore.go
index 77e6adac1..b0904102c 100644
--- a/api/bolt/datastore.go
+++ b/api/bolt/datastore.go
@@ -1,6 +1,7 @@
 package bolt
 
 import (
+	"io"
 	"log"
 	"path"
 	"time"
@@ -17,6 +18,7 @@ import (
 	"github.com/portainer/portainer/api/bolt/endpointrelation"
 	"github.com/portainer/portainer/api/bolt/errors"
 	"github.com/portainer/portainer/api/bolt/extension"
+	"github.com/portainer/portainer/api/bolt/internal"
 	"github.com/portainer/portainer/api/bolt/migrator"
 	"github.com/portainer/portainer/api/bolt/registry"
 	"github.com/portainer/portainer/api/bolt/resourcecontrol"
@@ -42,7 +44,7 @@ const (
 // BoltDB as the storage system.
 type Store struct {
 	path                    string
-	db                      *bolt.DB
+	connection              *internal.DbConnection
 	isNew                   bool
 	fileService             portainer.FileService
 	CustomTemplateService   *customtemplate.Service
@@ -83,6 +85,7 @@ func NewStore(storePath string, fileService portainer.FileService) (*Store, erro
 		path:        storePath,
 		fileService: fileService,
 		isNew:       true,
+		connection:  &internal.DbConnection{},
 	}
 
 	databasePath := path.Join(storePath, databaseFileName)
@@ -105,15 +108,15 @@ func (store *Store) Open() error {
 	if err != nil {
 		return err
 	}
-	store.db = db
+	store.connection.DB = db
 
 	return store.initServices()
 }
 
 // Close closes the BoltDB database.
 func (store *Store) Close() error {
-	if store.db != nil {
-		return store.db.Close()
+	if store.connection.DB != nil {
+		return store.connection.Close()
 	}
 	return nil
 }
@@ -134,8 +137,9 @@ func (store *Store) CheckCurrentEdition() error {
 
 // MigrateData automatically migrate the data based on the DBVersion.
 // This process is only triggered on an existing database, not if the database was just created.
-func (store *Store) MigrateData() error {
-	if store.isNew {
+// if force is true, then migrate regardless.
+func (store *Store) MigrateData(force bool) error {
+	if store.isNew && !force {
 		return store.VersionService.StoreDBVersion(portainer.DBVersion)
 	}
 
@@ -148,7 +152,7 @@ func (store *Store) MigrateData() error {
 
 	if version < portainer.DBVersion {
 		migratorParams := &migrator.Parameters{
-			DB:                      store.db,
+			DB:                      store.connection.DB,
 			DatabaseVersion:         version,
 			EndpointGroupService:    store.EndpointGroupService,
 			EndpointService:         store.EndpointService,
@@ -180,238 +184,11 @@ func (store *Store) MigrateData() error {
 	return nil
 }
 
-func (store *Store) initServices() error {
-	authorizationsetService, err := role.NewService(store.db)
-	if err != nil {
+// BackupTo backs up db to a provided writer.
+// It does hot backup and doesn't block other database reads and writes
+func (store *Store) BackupTo(w io.Writer) error {
+	return store.connection.View(func(tx *bolt.Tx) error {
+		_, err := tx.WriteTo(w)
 		return err
-	}
-	store.RoleService = authorizationsetService
-
-	customTemplateService, err := customtemplate.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.CustomTemplateService = customTemplateService
-
-	dockerhubService, err := dockerhub.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.DockerHubService = dockerhubService
-
-	edgeStackService, err := edgestack.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.EdgeStackService = edgeStackService
-
-	edgeGroupService, err := edgegroup.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.EdgeGroupService = edgeGroupService
-
-	edgeJobService, err := edgejob.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.EdgeJobService = edgeJobService
-
-	endpointgroupService, err := endpointgroup.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.EndpointGroupService = endpointgroupService
-
-	endpointService, err := endpoint.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.EndpointService = endpointService
-
-	endpointRelationService, err := endpointrelation.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.EndpointRelationService = endpointRelationService
-
-	extensionService, err := extension.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.ExtensionService = extensionService
-
-	registryService, err := registry.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.RegistryService = registryService
-
-	resourcecontrolService, err := resourcecontrol.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.ResourceControlService = resourcecontrolService
-
-	settingsService, err := settings.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.SettingsService = settingsService
-
-	stackService, err := stack.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.StackService = stackService
-
-	tagService, err := tag.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.TagService = tagService
-
-	teammembershipService, err := teammembership.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.TeamMembershipService = teammembershipService
-
-	teamService, err := team.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.TeamService = teamService
-
-	tunnelServerService, err := tunnelserver.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.TunnelServerService = tunnelServerService
-
-	userService, err := user.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.UserService = userService
-
-	versionService, err := version.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.VersionService = versionService
-
-	webhookService, err := webhook.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.WebhookService = webhookService
-
-	scheduleService, err := schedule.NewService(store.db)
-	if err != nil {
-		return err
-	}
-	store.ScheduleService = scheduleService
-
-	return nil
-}
-
-// CustomTemplate gives access to the CustomTemplate data management layer
-func (store *Store) CustomTemplate() portainer.CustomTemplateService {
-	return store.CustomTemplateService
-}
-
-// DockerHub gives access to the DockerHub data management layer
-func (store *Store) DockerHub() portainer.DockerHubService {
-	return store.DockerHubService
-}
-
-// EdgeGroup gives access to the EdgeGroup data management layer
-func (store *Store) EdgeGroup() portainer.EdgeGroupService {
-	return store.EdgeGroupService
-}
-
-// EdgeJob gives access to the EdgeJob data management layer
-func (store *Store) EdgeJob() portainer.EdgeJobService {
-	return store.EdgeJobService
-}
-
-// EdgeStack gives access to the EdgeStack data management layer
-func (store *Store) EdgeStack() portainer.EdgeStackService {
-	return store.EdgeStackService
-}
-
-// Endpoint gives access to the Endpoint data management layer
-func (store *Store) Endpoint() portainer.EndpointService {
-	return store.EndpointService
-}
-
-// EndpointGroup gives access to the EndpointGroup data management layer
-func (store *Store) EndpointGroup() portainer.EndpointGroupService {
-	return store.EndpointGroupService
-}
-
-// EndpointRelation gives access to the EndpointRelation data management layer
-func (store *Store) EndpointRelation() portainer.EndpointRelationService {
-	return store.EndpointRelationService
-}
-
-// Registry gives access to the Registry data management layer
-func (store *Store) Registry() portainer.RegistryService {
-	return store.RegistryService
-}
-
-// ResourceControl gives access to the ResourceControl data management layer
-func (store *Store) ResourceControl() portainer.ResourceControlService {
-	return store.ResourceControlService
-}
-
-// Role gives access to the Role data management layer
-func (store *Store) Role() portainer.RoleService {
-	return store.RoleService
-}
-
-// Settings gives access to the Settings data management layer
-func (store *Store) Settings() portainer.SettingsService {
-	return store.SettingsService
-}
-
-// Stack gives access to the Stack data management layer
-func (store *Store) Stack() portainer.StackService {
-	return store.StackService
-}
-
-// Tag gives access to the Tag data management layer
-func (store *Store) Tag() portainer.TagService {
-	return store.TagService
-}
-
-// TeamMembership gives access to the TeamMembership data management layer
-func (store *Store) TeamMembership() portainer.TeamMembershipService {
-	return store.TeamMembershipService
-}
-
-// Team gives access to the Team data management layer
-func (store *Store) Team() portainer.TeamService {
-	return store.TeamService
-}
-
-// TunnelServer gives access to the TunnelServer data management layer
-func (store *Store) TunnelServer() portainer.TunnelServerService {
-	return store.TunnelServerService
-}
-
-// User gives access to the User data management layer
-func (store *Store) User() portainer.UserService {
-	return store.UserService
-}
-
-// Version gives access to the Version data management layer
-func (store *Store) Version() portainer.VersionService {
-	return store.VersionService
-}
-
-// Webhook gives access to the Webhook data management layer
-func (store *Store) Webhook() portainer.WebhookService {
-	return store.WebhookService
+	})
 }
diff --git a/api/bolt/dockerhub/dockerhub.go b/api/bolt/dockerhub/dockerhub.go
index a225be462..f39c32a8b 100644
--- a/api/bolt/dockerhub/dockerhub.go
+++ b/api/bolt/dockerhub/dockerhub.go
@@ -1,10 +1,8 @@
 package dockerhub
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
-
-	"github.com/boltdb/bolt"
 )
 
 const (
@@ -15,18 +13,18 @@ const (
 
 // Service represents a service for managing Dockerhub data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) DockerHub() (*portainer.DockerHub, error) {
 	var dockerhub portainer.DockerHub
 
-	err := internal.GetObject(service.db, BucketName, []byte(dockerHubKey), &dockerhub)
+	err := internal.GetObject(service.connection, BucketName, []byte(dockerHubKey), &dockerhub)
 	if err != nil {
 		return nil, err
 	}
@@ -44,5 +42,5 @@ func (service *Service) DockerHub() (*portainer.DockerHub, error) {
 
 // UpdateDockerHub updates a DockerHub object.
 func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
-	return internal.UpdateObject(service.db, BucketName, []byte(dockerHubKey), dockerhub)
+	return internal.UpdateObject(service.connection, BucketName, []byte(dockerHubKey), dockerhub)
 }
diff --git a/api/bolt/edgegroup/edgegroup.go b/api/bolt/edgegroup/edgegroup.go
index 41909b437..d7dc2c60b 100644
--- a/api/bolt/edgegroup/edgegroup.go
+++ b/api/bolt/edgegroup/edgegroup.go
@@ -2,7 +2,7 @@ package edgegroup
 
 import (
 	"github.com/boltdb/bolt"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 )
 
@@ -13,18 +13,18 @@ const (
 
 // Service represents a service for managing Edge group data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
 	var groups = make([]portainer.EdgeGroup, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -56,7 +56,7 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
 	var group portainer.EdgeGroup
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &group)
+	err := internal.GetObject(service.connection, BucketName, identifier, &group)
 	if err != nil {
 		return nil, err
 	}
@@ -67,18 +67,18 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
 // UpdateEdgeGroup updates an Edge group.
 func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, group)
+	return internal.UpdateObject(service.connection, BucketName, identifier, group)
 }
 
 // DeleteEdgeGroup deletes an Edge group.
 func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // CreateEdgeGroup assign an ID to a new Edge group and saves it.
 func (service *Service) CreateEdgeGroup(group *portainer.EdgeGroup) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
diff --git a/api/bolt/edgejob/edgejob.go b/api/bolt/edgejob/edgejob.go
index f3354c7d8..216bdacec 100644
--- a/api/bolt/edgejob/edgejob.go
+++ b/api/bolt/edgejob/edgejob.go
@@ -2,7 +2,7 @@ package edgejob
 
 import (
 	"github.com/boltdb/bolt"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 )
 
@@ -13,18 +13,18 @@ const (
 
 // Service represents a service for managing edge jobs data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
 	var edgeJobs = make([]portainer.EdgeJob, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -56,7 +56,7 @@ func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, err
 	var edgeJob portainer.EdgeJob
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &edgeJob)
+	err := internal.GetObject(service.connection, BucketName, identifier, &edgeJob)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, err
 
 // CreateEdgeJob creates a new Edge job
 func (service *Service) CreateEdgeJob(edgeJob *portainer.EdgeJob) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		if edgeJob.ID == 0 {
@@ -86,16 +86,16 @@ func (service *Service) CreateEdgeJob(edgeJob *portainer.EdgeJob) error {
 // UpdateEdgeJob updates an Edge job by ID
 func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, edgeJob)
+	return internal.UpdateObject(service.connection, BucketName, identifier, edgeJob)
 }
 
 // DeleteEdgeJob deletes an Edge job
 func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // GetNextIdentifier returns the next identifier for an endpoint.
 func (service *Service) GetNextIdentifier() int {
-	return internal.GetNextIdentifier(service.db, BucketName)
+	return internal.GetNextIdentifier(service.connection, BucketName)
 }
diff --git a/api/bolt/edgestack/edgestack.go b/api/bolt/edgestack/edgestack.go
index 337bb6892..ff58c0dae 100644
--- a/api/bolt/edgestack/edgestack.go
+++ b/api/bolt/edgestack/edgestack.go
@@ -2,7 +2,7 @@ package edgestack
 
 import (
 	"github.com/boltdb/bolt"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 )
 
@@ -13,18 +13,18 @@ const (
 
 // Service represents a service for managing Edge stack data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
 	var stacks = make([]portainer.EdgeStack, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -56,7 +56,7 @@ func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStac
 	var stack portainer.EdgeStack
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &stack)
+	err := internal.GetObject(service.connection, BucketName, identifier, &stack)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStac
 
 // CreateEdgeStack assign an ID to a new Edge stack and saves it.
 func (service *Service) CreateEdgeStack(edgeStack *portainer.EdgeStack) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		if edgeStack.ID == 0 {
@@ -86,16 +86,16 @@ func (service *Service) CreateEdgeStack(edgeStack *portainer.EdgeStack) error {
 // UpdateEdgeStack updates an Edge stack.
 func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, edgeStack)
+	return internal.UpdateObject(service.connection, BucketName, identifier, edgeStack)
 }
 
 // DeleteEdgeStack deletes an Edge stack.
 func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // GetNextIdentifier returns the next identifier for an endpoint.
 func (service *Service) GetNextIdentifier() int {
-	return internal.GetNextIdentifier(service.db, BucketName)
+	return internal.GetNextIdentifier(service.connection, BucketName)
 }
diff --git a/api/bolt/endpoint/endpoint.go b/api/bolt/endpoint/endpoint.go
index 69d9dc4ac..ebd162985 100644
--- a/api/bolt/endpoint/endpoint.go
+++ b/api/bolt/endpoint/endpoint.go
@@ -2,7 +2,7 @@ package endpoint
 
 import (
 	"github.com/boltdb/bolt"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 )
 
@@ -13,18 +13,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -33,7 +33,7 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
 	var endpoint portainer.Endpoint
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &endpoint)
+	err := internal.GetObject(service.connection, BucketName, identifier, &endpoint)
 	if err != nil {
 		return nil, err
 	}
@@ -44,20 +44,20 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
 // UpdateEndpoint updates an endpoint.
 func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, endpoint)
+	return internal.UpdateObject(service.connection, BucketName, identifier, endpoint)
 }
 
 // DeleteEndpoint deletes an endpoint.
 func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // Endpoints return an array containing all the endpoints.
 func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
 	var endpoints = make([]portainer.Endpoint, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -78,7 +78,7 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
 
 // CreateEndpoint assign an ID to a new endpoint and saves it.
 func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		// We manually manage sequences for endpoints
@@ -98,12 +98,12 @@ func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
 
 // GetNextIdentifier returns the next identifier for an endpoint.
 func (service *Service) GetNextIdentifier() int {
-	return internal.GetNextIdentifier(service.db, BucketName)
+	return internal.GetNextIdentifier(service.connection, BucketName)
 }
 
 // Synchronize creates, updates and deletes endpoints inside a single transaction.
 func (service *Service) Synchronize(toCreate, toUpdate, toDelete []*portainer.Endpoint) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		for _, endpoint := range toCreate {
diff --git a/api/bolt/endpointgroup/endpointgroup.go b/api/bolt/endpointgroup/endpointgroup.go
index 3311e88cb..02c0e3382 100644
--- a/api/bolt/endpointgroup/endpointgroup.go
+++ b/api/bolt/endpointgroup/endpointgroup.go
@@ -1,7 +1,7 @@
 package endpointgroup
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.
 	var endpointGroup portainer.EndpointGroup
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &endpointGroup)
+	err := internal.GetObject(service.connection, BucketName, identifier, &endpointGroup)
 	if err != nil {
 		return nil, err
 	}
@@ -45,20 +45,20 @@ func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.
 // UpdateEndpointGroup updates an endpoint group.
 func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, endpointGroup)
+	return internal.UpdateObject(service.connection, BucketName, identifier, endpointGroup)
 }
 
 // DeleteEndpointGroup deletes an endpoint group.
 func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // EndpointGroups return an array containing all the endpoint groups.
 func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
 	var endpointGroups = make([]portainer.EndpointGroup, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -79,7 +79,7 @@ func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
 
 // CreateEndpointGroup assign an ID to a new endpoint group and saves it.
 func (service *Service) CreateEndpointGroup(endpointGroup *portainer.EndpointGroup) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
diff --git a/api/bolt/endpointrelation/endpointrelation.go b/api/bolt/endpointrelation/endpointrelation.go
index 00dab3f4a..974913531 100644
--- a/api/bolt/endpointrelation/endpointrelation.go
+++ b/api/bolt/endpointrelation/endpointrelation.go
@@ -13,18 +13,18 @@ const (
 
 // Service represents a service for managing endpoint relation data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -33,7 +33,7 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
 	var endpointRelation portainer.EndpointRelation
 	identifier := internal.Itob(int(endpointID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &endpointRelation)
+	err := internal.GetObject(service.connection, BucketName, identifier, &endpointRelation)
 	if err != nil {
 		return nil, err
 	}
@@ -43,7 +43,7 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
 
 // CreateEndpointRelation saves endpointRelation
 func (service *Service) CreateEndpointRelation(endpointRelation *portainer.EndpointRelation) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		data, err := internal.MarshalObject(endpointRelation)
@@ -58,11 +58,11 @@ func (service *Service) CreateEndpointRelation(endpointRelation *portainer.Endpo
 // UpdateEndpointRelation updates an Endpoint relation object
 func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
 	identifier := internal.Itob(int(EndpointID))
-	return internal.UpdateObject(service.db, BucketName, identifier, endpointRelation)
+	return internal.UpdateObject(service.connection, BucketName, identifier, endpointRelation)
 }
 
 // DeleteEndpointRelation deletes an Endpoint relation object
 func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
 	identifier := internal.Itob(int(EndpointID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/extension/extension.go b/api/bolt/extension/extension.go
index 83e225414..15104af8f 100644
--- a/api/bolt/extension/extension.go
+++ b/api/bolt/extension/extension.go
@@ -1,7 +1,7 @@
 package extension
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
 	var extension portainer.Extension
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &extension)
+	err := internal.GetObject(service.connection, BucketName, identifier, &extension)
 	if err != nil {
 		return nil, err
 	}
@@ -46,7 +46,7 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
 func (service *Service) Extensions() ([]portainer.Extension, error) {
 	var extensions = make([]portainer.Extension, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -67,7 +67,7 @@ func (service *Service) Extensions() ([]portainer.Extension, error) {
 
 // Persist persists a extension inside the database.
 func (service *Service) Persist(extension *portainer.Extension) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		data, err := internal.MarshalObject(extension)
@@ -82,5 +82,5 @@ func (service *Service) Persist(extension *portainer.Extension) error {
 // DeleteExtension deletes a Extension.
 func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/internal/db.go b/api/bolt/internal/db.go
index 101e1ff00..a90cf2adc 100644
--- a/api/bolt/internal/db.go
+++ b/api/bolt/internal/db.go
@@ -7,6 +7,10 @@ import (
 	"github.com/portainer/portainer/api/bolt/errors"
 )
 
+type DbConnection struct {
+	*bolt.DB
+}
+
 // Itob returns an 8-byte big endian representation of v.
 // This function is typically used for encoding integer IDs to byte slices
 // so that they can be used as BoltDB keys.
@@ -17,8 +21,8 @@ func Itob(v int) []byte {
 }
 
 // CreateBucket is a generic function used to create a bucket inside a bolt database.
-func CreateBucket(db *bolt.DB, bucketName string) error {
-	return db.Update(func(tx *bolt.Tx) error {
+func CreateBucket(connection *DbConnection, bucketName string) error {
+	return connection.Update(func(tx *bolt.Tx) error {
 		_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
 		if err != nil {
 			return err
@@ -28,10 +32,10 @@ func CreateBucket(db *bolt.DB, bucketName string) error {
 }
 
 // GetObject is a generic function used to retrieve an unmarshalled object from a bolt database.
-func GetObject(db *bolt.DB, bucketName string, key []byte, object interface{}) error {
+func GetObject(connection *DbConnection, bucketName string, key []byte, object interface{}) error {
 	var data []byte
 
-	err := db.View(func(tx *bolt.Tx) error {
+	err := connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(bucketName))
 
 		value := bucket.Get(key)
@@ -52,8 +56,8 @@ func GetObject(db *bolt.DB, bucketName string, key []byte, object interface{}) e
 }
 
 // UpdateObject is a generic function used to update an object inside a bolt database.
-func UpdateObject(db *bolt.DB, bucketName string, key []byte, object interface{}) error {
-	return db.Update(func(tx *bolt.Tx) error {
+func UpdateObject(connection *DbConnection, bucketName string, key []byte, object interface{}) error {
+	return connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(bucketName))
 
 		data, err := MarshalObject(object)
@@ -71,18 +75,18 @@ func UpdateObject(db *bolt.DB, bucketName string, key []byte, object interface{}
 }
 
 // DeleteObject is a generic function used to delete an object inside a bolt database.
-func DeleteObject(db *bolt.DB, bucketName string, key []byte) error {
-	return db.Update(func(tx *bolt.Tx) error {
+func DeleteObject(connection *DbConnection, bucketName string, key []byte) error {
+	return connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(bucketName))
 		return bucket.Delete(key)
 	})
 }
 
 // GetNextIdentifier is a generic function that returns the specified bucket identifier incremented by 1.
-func GetNextIdentifier(db *bolt.DB, bucketName string) int {
+func GetNextIdentifier(connection *DbConnection, bucketName string) int {
 	var identifier int
 
-	db.Update(func(tx *bolt.Tx) error {
+	connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(bucketName))
 		id, err := bucket.NextSequence()
 		if err != nil {
diff --git a/api/bolt/registry/registry.go b/api/bolt/registry/registry.go
index 428c6957e..dc741ae7b 100644
--- a/api/bolt/registry/registry.go
+++ b/api/bolt/registry/registry.go
@@ -1,7 +1,7 @@
 package registry
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry,
 	var registry portainer.Registry
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &registry)
+	err := internal.GetObject(service.connection, BucketName, identifier, &registry)
 	if err != nil {
 		return nil, err
 	}
@@ -46,7 +46,7 @@ func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry,
 func (service *Service) Registries() ([]portainer.Registry, error) {
 	var registries = make([]portainer.Registry, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -67,7 +67,7 @@ func (service *Service) Registries() ([]portainer.Registry, error) {
 
 // CreateRegistry creates a new registry.
 func (service *Service) CreateRegistry(registry *portainer.Registry) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
@@ -85,11 +85,11 @@ func (service *Service) CreateRegistry(registry *portainer.Registry) error {
 // UpdateRegistry updates an registry.
 func (service *Service) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, registry)
+	return internal.UpdateObject(service.connection, BucketName, identifier, registry)
 }
 
 // DeleteRegistry deletes an registry.
 func (service *Service) DeleteRegistry(ID portainer.RegistryID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/resourcecontrol/resourcecontrol.go b/api/bolt/resourcecontrol/resourcecontrol.go
index ef07aff03..d0d1559fb 100644
--- a/api/bolt/resourcecontrol/resourcecontrol.go
+++ b/api/bolt/resourcecontrol/resourcecontrol.go
@@ -1,7 +1,7 @@
 package resourcecontrol
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portai
 	var resourceControl portainer.ResourceControl
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &resourceControl)
+	err := internal.GetObject(service.connection, BucketName, identifier, &resourceControl)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portai
 func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
 	var resourceControl *portainer.ResourceControl
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 		cursor := bucket.Cursor()
 
@@ -82,7 +82,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
 func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
 	var rcs = make([]portainer.ResourceControl, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -103,7 +103,7 @@ func (service *Service) ResourceControls() ([]portainer.ResourceControl, error)
 
 // CreateResourceControl creates a new ResourceControl object
 func (service *Service) CreateResourceControl(resourceControl *portainer.ResourceControl) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
@@ -121,11 +121,11 @@ func (service *Service) CreateResourceControl(resourceControl *portainer.Resourc
 // UpdateResourceControl saves a ResourceControl object.
 func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, resourceControl)
+	return internal.UpdateObject(service.connection, BucketName, identifier, resourceControl)
 }
 
 // DeleteResourceControl deletes a ResourceControl object by ID
 func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/role/role.go b/api/bolt/role/role.go
index 36cd8e7d1..eff9d56f1 100644
--- a/api/bolt/role/role.go
+++ b/api/bolt/role/role.go
@@ -1,7 +1,7 @@
 package role
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
 	var set portainer.Role
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &set)
+	err := internal.GetObject(service.connection, BucketName, identifier, &set)
 	if err != nil {
 		return nil, err
 	}
@@ -46,7 +46,7 @@ func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
 func (service *Service) Roles() ([]portainer.Role, error) {
 	var sets = make([]portainer.Role, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -67,7 +67,7 @@ func (service *Service) Roles() ([]portainer.Role, error) {
 
 // CreateRole creates a new Role.
 func (service *Service) CreateRole(role *portainer.Role) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
@@ -85,5 +85,5 @@ func (service *Service) CreateRole(role *portainer.Role) error {
 // UpdateRole updates a role.
 func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, role)
+	return internal.UpdateObject(service.connection, BucketName, identifier, role)
 }
diff --git a/api/bolt/schedule/schedule.go b/api/bolt/schedule/schedule.go
index 25b996373..d919586d8 100644
--- a/api/bolt/schedule/schedule.go
+++ b/api/bolt/schedule/schedule.go
@@ -1,7 +1,7 @@
 package schedule
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing schedule data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func (service *Service) Schedule(ID portainer.ScheduleID) (*portainer.Schedule,
 	var schedule portainer.Schedule
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &schedule)
+	err := internal.GetObject(service.connection, BucketName, identifier, &schedule)
 	if err != nil {
 		return nil, err
 	}
@@ -45,20 +45,20 @@ func (service *Service) Schedule(ID portainer.ScheduleID) (*portainer.Schedule,
 // UpdateSchedule updates a schedule.
 func (service *Service) UpdateSchedule(ID portainer.ScheduleID, schedule *portainer.Schedule) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, schedule)
+	return internal.UpdateObject(service.connection, BucketName, identifier, schedule)
 }
 
 // DeleteSchedule deletes a schedule.
 func (service *Service) DeleteSchedule(ID portainer.ScheduleID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // Schedules return a array containing all the schedules.
 func (service *Service) Schedules() ([]portainer.Schedule, error) {
 	var schedules = make([]portainer.Schedule, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -82,7 +82,7 @@ func (service *Service) Schedules() ([]portainer.Schedule, error) {
 func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portainer.Schedule, error) {
 	var schedules = make([]portainer.Schedule, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -105,7 +105,7 @@ func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portain
 
 // CreateSchedule assign an ID to a new schedule and saves it.
 func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		// We manually manage sequences for schedules
@@ -125,5 +125,5 @@ func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
 
 // GetNextIdentifier returns the next identifier for a schedule.
 func (service *Service) GetNextIdentifier() int {
-	return internal.GetNextIdentifier(service.db, BucketName)
+	return internal.GetNextIdentifier(service.connection, BucketName)
 }
diff --git a/api/bolt/services.go b/api/bolt/services.go
new file mode 100644
index 000000000..4cdc84069
--- /dev/null
+++ b/api/bolt/services.go
@@ -0,0 +1,263 @@
+package bolt
+
+import (
+	portainer "github.com/portainer/portainer/api"
+	"github.com/portainer/portainer/api/bolt/customtemplate"
+	"github.com/portainer/portainer/api/bolt/dockerhub"
+	"github.com/portainer/portainer/api/bolt/edgegroup"
+	"github.com/portainer/portainer/api/bolt/edgejob"
+	"github.com/portainer/portainer/api/bolt/edgestack"
+	"github.com/portainer/portainer/api/bolt/endpoint"
+	"github.com/portainer/portainer/api/bolt/endpointgroup"
+	"github.com/portainer/portainer/api/bolt/endpointrelation"
+	"github.com/portainer/portainer/api/bolt/extension"
+	"github.com/portainer/portainer/api/bolt/registry"
+	"github.com/portainer/portainer/api/bolt/resourcecontrol"
+	"github.com/portainer/portainer/api/bolt/role"
+	"github.com/portainer/portainer/api/bolt/schedule"
+	"github.com/portainer/portainer/api/bolt/settings"
+	"github.com/portainer/portainer/api/bolt/stack"
+	"github.com/portainer/portainer/api/bolt/tag"
+	"github.com/portainer/portainer/api/bolt/team"
+	"github.com/portainer/portainer/api/bolt/teammembership"
+	"github.com/portainer/portainer/api/bolt/tunnelserver"
+	"github.com/portainer/portainer/api/bolt/user"
+	"github.com/portainer/portainer/api/bolt/version"
+	"github.com/portainer/portainer/api/bolt/webhook"
+)
+
+func (store *Store) initServices() error {
+	authorizationsetService, err := role.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.RoleService = authorizationsetService
+
+	customTemplateService, err := customtemplate.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.CustomTemplateService = customTemplateService
+
+	dockerhubService, err := dockerhub.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.DockerHubService = dockerhubService
+
+	edgeStackService, err := edgestack.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.EdgeStackService = edgeStackService
+
+	edgeGroupService, err := edgegroup.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.EdgeGroupService = edgeGroupService
+
+	edgeJobService, err := edgejob.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.EdgeJobService = edgeJobService
+
+	endpointgroupService, err := endpointgroup.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.EndpointGroupService = endpointgroupService
+
+	endpointService, err := endpoint.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.EndpointService = endpointService
+
+	endpointRelationService, err := endpointrelation.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.EndpointRelationService = endpointRelationService
+
+	extensionService, err := extension.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.ExtensionService = extensionService
+
+	registryService, err := registry.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.RegistryService = registryService
+
+	resourcecontrolService, err := resourcecontrol.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.ResourceControlService = resourcecontrolService
+
+	settingsService, err := settings.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.SettingsService = settingsService
+
+	stackService, err := stack.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.StackService = stackService
+
+	tagService, err := tag.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.TagService = tagService
+
+	teammembershipService, err := teammembership.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.TeamMembershipService = teammembershipService
+
+	teamService, err := team.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.TeamService = teamService
+
+	tunnelServerService, err := tunnelserver.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.TunnelServerService = tunnelServerService
+
+	userService, err := user.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.UserService = userService
+
+	versionService, err := version.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.VersionService = versionService
+
+	webhookService, err := webhook.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.WebhookService = webhookService
+
+	scheduleService, err := schedule.NewService(store.connection)
+	if err != nil {
+		return err
+	}
+	store.ScheduleService = scheduleService
+
+	return nil
+}
+
+// CustomTemplate gives access to the CustomTemplate data management layer
+func (store *Store) CustomTemplate() portainer.CustomTemplateService {
+	return store.CustomTemplateService
+}
+
+// DockerHub gives access to the DockerHub data management layer
+func (store *Store) DockerHub() portainer.DockerHubService {
+	return store.DockerHubService
+}
+
+// EdgeGroup gives access to the EdgeGroup data management layer
+func (store *Store) EdgeGroup() portainer.EdgeGroupService {
+	return store.EdgeGroupService
+}
+
+// EdgeJob gives access to the EdgeJob data management layer
+func (store *Store) EdgeJob() portainer.EdgeJobService {
+	return store.EdgeJobService
+}
+
+// EdgeStack gives access to the EdgeStack data management layer
+func (store *Store) EdgeStack() portainer.EdgeStackService {
+	return store.EdgeStackService
+}
+
+// Endpoint gives access to the Endpoint data management layer
+func (store *Store) Endpoint() portainer.EndpointService {
+	return store.EndpointService
+}
+
+// EndpointGroup gives access to the EndpointGroup data management layer
+func (store *Store) EndpointGroup() portainer.EndpointGroupService {
+	return store.EndpointGroupService
+}
+
+// EndpointRelation gives access to the EndpointRelation data management layer
+func (store *Store) EndpointRelation() portainer.EndpointRelationService {
+	return store.EndpointRelationService
+}
+
+// Registry gives access to the Registry data management layer
+func (store *Store) Registry() portainer.RegistryService {
+	return store.RegistryService
+}
+
+// ResourceControl gives access to the ResourceControl data management layer
+func (store *Store) ResourceControl() portainer.ResourceControlService {
+	return store.ResourceControlService
+}
+
+// Role gives access to the Role data management layer
+func (store *Store) Role() portainer.RoleService {
+	return store.RoleService
+}
+
+// Settings gives access to the Settings data management layer
+func (store *Store) Settings() portainer.SettingsService {
+	return store.SettingsService
+}
+
+// Stack gives access to the Stack data management layer
+func (store *Store) Stack() portainer.StackService {
+	return store.StackService
+}
+
+// Tag gives access to the Tag data management layer
+func (store *Store) Tag() portainer.TagService {
+	return store.TagService
+}
+
+// TeamMembership gives access to the TeamMembership data management layer
+func (store *Store) TeamMembership() portainer.TeamMembershipService {
+	return store.TeamMembershipService
+}
+
+// Team gives access to the Team data management layer
+func (store *Store) Team() portainer.TeamService {
+	return store.TeamService
+}
+
+// TunnelServer gives access to the TunnelServer data management layer
+func (store *Store) TunnelServer() portainer.TunnelServerService {
+	return store.TunnelServerService
+}
+
+// User gives access to the User data management layer
+func (store *Store) User() portainer.UserService {
+	return store.UserService
+}
+
+// Version gives access to the Version data management layer
+func (store *Store) Version() portainer.VersionService {
+	return store.VersionService
+}
+
+// Webhook gives access to the Webhook data management layer
+func (store *Store) Webhook() portainer.WebhookService {
+	return store.WebhookService
+}
diff --git a/api/bolt/settings/settings.go b/api/bolt/settings/settings.go
index c14032b25..001bd6142 100644
--- a/api/bolt/settings/settings.go
+++ b/api/bolt/settings/settings.go
@@ -1,10 +1,8 @@
 package settings
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
-
-	"github.com/boltdb/bolt"
 )
 
 const (
@@ -15,18 +13,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) Settings() (*portainer.Settings, error) {
 	var settings portainer.Settings
 
-	err := internal.GetObject(service.db, BucketName, []byte(settingsKey), &settings)
+	err := internal.GetObject(service.connection, BucketName, []byte(settingsKey), &settings)
 	if err != nil {
 		return nil, err
 	}
@@ -44,5 +42,5 @@ func (service *Service) Settings() (*portainer.Settings, error) {
 
 // UpdateSettings persists a Settings object.
 func (service *Service) UpdateSettings(settings *portainer.Settings) error {
-	return internal.UpdateObject(service.db, BucketName, []byte(settingsKey), settings)
+	return internal.UpdateObject(service.connection, BucketName, []byte(settingsKey), settings)
 }
diff --git a/api/bolt/stack/stack.go b/api/bolt/stack/stack.go
index a5145ba35..f9cfafad7 100644
--- a/api/bolt/stack/stack.go
+++ b/api/bolt/stack/stack.go
@@ -1,7 +1,7 @@
 package stack
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/errors"
 	"github.com/portainer/portainer/api/bolt/internal"
 
@@ -15,18 +15,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -35,7 +35,7 @@ func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) {
 	var stack portainer.Stack
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &stack)
+	err := internal.GetObject(service.connection, BucketName, identifier, &stack)
 	if err != nil {
 		return nil, err
 	}
@@ -47,7 +47,7 @@ func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) {
 func (service *Service) StackByName(name string) (*portainer.Stack, error) {
 	var stack *portainer.Stack
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 		cursor := bucket.Cursor()
 
@@ -78,7 +78,7 @@ func (service *Service) StackByName(name string) (*portainer.Stack, error) {
 func (service *Service) Stacks() ([]portainer.Stack, error) {
 	var stacks = make([]portainer.Stack, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -99,12 +99,12 @@ func (service *Service) Stacks() ([]portainer.Stack, error) {
 
 // GetNextIdentifier returns the next identifier for a stack.
 func (service *Service) GetNextIdentifier() int {
-	return internal.GetNextIdentifier(service.db, BucketName)
+	return internal.GetNextIdentifier(service.connection, BucketName)
 }
 
 // CreateStack creates a new stack.
 func (service *Service) CreateStack(stack *portainer.Stack) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		// We manually manage sequences for stacks
@@ -125,11 +125,11 @@ func (service *Service) CreateStack(stack *portainer.Stack) error {
 // UpdateStack updates a stack.
 func (service *Service) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, stack)
+	return internal.UpdateObject(service.connection, BucketName, identifier, stack)
 }
 
 // DeleteStack deletes a stack.
 func (service *Service) DeleteStack(ID portainer.StackID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/tag/tag.go b/api/bolt/tag/tag.go
index ba0f44ba9..f10c64d35 100644
--- a/api/bolt/tag/tag.go
+++ b/api/bolt/tag/tag.go
@@ -1,7 +1,7 @@
 package tag
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -33,7 +33,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) Tags() ([]portainer.Tag, error) {
 	var tags = make([]portainer.Tag, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -57,7 +57,7 @@ func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
 	var tag portainer.Tag
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &tag)
+	err := internal.GetObject(service.connection, BucketName, identifier, &tag)
 	if err != nil {
 		return nil, err
 	}
@@ -67,7 +67,7 @@ func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
 
 // CreateTag creates a new tag.
 func (service *Service) CreateTag(tag *portainer.Tag) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
@@ -85,11 +85,11 @@ func (service *Service) CreateTag(tag *portainer.Tag) error {
 // UpdateTag updates a tag.
 func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, tag)
+	return internal.UpdateObject(service.connection, BucketName, identifier, tag)
 }
 
 // DeleteTag deletes a tag.
 func (service *Service) DeleteTag(ID portainer.TagID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/team/team.go b/api/bolt/team/team.go
index a503e8285..d710f05c1 100644
--- a/api/bolt/team/team.go
+++ b/api/bolt/team/team.go
@@ -17,18 +17,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -37,7 +37,7 @@ func (service *Service) Team(ID portainer.TeamID) (*portainer.Team, error) {
 	var team portainer.Team
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &team)
+	err := internal.GetObject(service.connection, BucketName, identifier, &team)
 	if err != nil {
 		return nil, err
 	}
@@ -49,7 +49,7 @@ func (service *Service) Team(ID portainer.TeamID) (*portainer.Team, error) {
 func (service *Service) TeamByName(name string) (*portainer.Team, error) {
 	var team *portainer.Team
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -80,7 +80,7 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
 func (service *Service) Teams() ([]portainer.Team, error) {
 	var teams = make([]portainer.Team, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -102,12 +102,12 @@ func (service *Service) Teams() ([]portainer.Team, error) {
 // UpdateTeam saves a Team.
 func (service *Service) UpdateTeam(ID portainer.TeamID, team *portainer.Team) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, team)
+	return internal.UpdateObject(service.connection, BucketName, identifier, team)
 }
 
 // CreateTeam creates a new Team.
 func (service *Service) CreateTeam(team *portainer.Team) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
@@ -125,5 +125,5 @@ func (service *Service) CreateTeam(team *portainer.Team) error {
 // DeleteTeam deletes a Team.
 func (service *Service) DeleteTeam(ID portainer.TeamID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/teammembership/teammembership.go b/api/bolt/teammembership/teammembership.go
index af60db0d2..752120ea1 100644
--- a/api/bolt/teammembership/teammembership.go
+++ b/api/bolt/teammembership/teammembership.go
@@ -1,7 +1,7 @@
 package teammembership
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
 
 	"github.com/boltdb/bolt"
@@ -14,18 +14,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func (service *Service) TeamMembership(ID portainer.TeamMembershipID) (*portaine
 	var membership portainer.TeamMembership
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &membership)
+	err := internal.GetObject(service.connection, BucketName, identifier, &membership)
 	if err != nil {
 		return nil, err
 	}
@@ -46,7 +46,7 @@ func (service *Service) TeamMembership(ID portainer.TeamMembershipID) (*portaine
 func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
 	var memberships = make([]portainer.TeamMembership, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -69,7 +69,7 @@ func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
 func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error) {
 	var memberships = make([]portainer.TeamMembership, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -95,7 +95,7 @@ func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]port
 func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error) {
 	var memberships = make([]portainer.TeamMembership, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -120,12 +120,12 @@ func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]port
 // UpdateTeamMembership saves a TeamMembership object.
 func (service *Service) UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error {
 	identifier := internal.Itob(int(ID))
-	return internal.UpdateObject(service.db, BucketName, identifier, membership)
+	return internal.UpdateObject(service.connection, BucketName, identifier, membership)
 }
 
 // CreateTeamMembership creates a new TeamMembership object.
 func (service *Service) CreateTeamMembership(membership *portainer.TeamMembership) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
@@ -143,12 +143,12 @@ func (service *Service) CreateTeamMembership(membership *portainer.TeamMembershi
 // DeleteTeamMembership deletes a TeamMembership object.
 func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // DeleteTeamMembershipByUserID deletes all the TeamMembership object associated to a UserID.
 func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -173,7 +173,7 @@ func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) er
 
 // DeleteTeamMembershipByTeamID deletes all the TeamMembership object associated to a TeamID.
 func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
diff --git a/api/bolt/tunnelserver/tunnelserver.go b/api/bolt/tunnelserver/tunnelserver.go
index 52ba4c101..a85b098df 100644
--- a/api/bolt/tunnelserver/tunnelserver.go
+++ b/api/bolt/tunnelserver/tunnelserver.go
@@ -1,10 +1,8 @@
 package tunnelserver
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/internal"
-
-	"github.com/boltdb/bolt"
 )
 
 const (
@@ -15,18 +13,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
 	var info portainer.TunnelServerInfo
 
-	err := internal.GetObject(service.db, BucketName, []byte(infoKey), &info)
+	err := internal.GetObject(service.connection, BucketName, []byte(infoKey), &info)
 	if err != nil {
 		return nil, err
 	}
@@ -44,5 +42,5 @@ func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
 
 // UpdateInfo persists a TunnelServerInfo object.
 func (service *Service) UpdateInfo(settings *portainer.TunnelServerInfo) error {
-	return internal.UpdateObject(service.db, BucketName, []byte(infoKey), settings)
+	return internal.UpdateObject(service.connection, BucketName, []byte(infoKey), settings)
 }
diff --git a/api/bolt/user/user.go b/api/bolt/user/user.go
index de628e8d4..700d2f419 100644
--- a/api/bolt/user/user.go
+++ b/api/bolt/user/user.go
@@ -17,18 +17,18 @@ const (
 
 // Service represents a service for managing endpoint data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -37,7 +37,7 @@ func (service *Service) User(ID portainer.UserID) (*portainer.User, error) {
 	var user portainer.User
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &user)
+	err := internal.GetObject(service.connection, BucketName, identifier, &user)
 	if err != nil {
 		return nil, err
 	}
@@ -51,7 +51,7 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
 
 	username = strings.ToLower(username)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 		cursor := bucket.Cursor()
 
@@ -81,7 +81,7 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
 func (service *Service) Users() ([]portainer.User, error) {
 	var users = make([]portainer.User, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -103,7 +103,7 @@ func (service *Service) Users() ([]portainer.User, error) {
 // UsersByRole return an array containing all the users with the specified role.
 func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
 	var users = make([]portainer.User, 0)
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -128,12 +128,12 @@ func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User,
 func (service *Service) UpdateUser(ID portainer.UserID, user *portainer.User) error {
 	identifier := internal.Itob(int(ID))
 	user.Username = strings.ToLower(user.Username)
-	return internal.UpdateObject(service.db, BucketName, identifier, user)
+	return internal.UpdateObject(service.connection, BucketName, identifier, user)
 }
 
 // CreateUser creates a new user.
 func (service *Service) CreateUser(user *portainer.User) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
@@ -152,5 +152,5 @@ func (service *Service) CreateUser(user *portainer.User) error {
 // DeleteUser deletes a user.
 func (service *Service) DeleteUser(ID portainer.UserID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
diff --git a/api/bolt/version/version.go b/api/bolt/version/version.go
index f879d2759..c697173ff 100644
--- a/api/bolt/version/version.go
+++ b/api/bolt/version/version.go
@@ -19,18 +19,18 @@ const (
 
 // Service represents a service to manage stored versions.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -38,7 +38,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) DBVersion() (int, error) {
 	var data []byte
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		value := bucket.Get([]byte(versionKey))
@@ -75,7 +75,7 @@ func (service *Service) Edition() (portainer.SoftwareEdition, error) {
 
 // StoreDBVersion store the database version.
 func (service *Service) StoreDBVersion(version int) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		data := []byte(strconv.Itoa(version))
@@ -87,7 +87,7 @@ func (service *Service) StoreDBVersion(version int) error {
 func (service *Service) InstanceID() (string, error) {
 	var data []byte
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		value := bucket.Get([]byte(instanceKey))
@@ -109,7 +109,7 @@ func (service *Service) InstanceID() (string, error) {
 
 // StoreInstanceID store the instance ID.
 func (service *Service) StoreInstanceID(ID string) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		data := []byte(ID)
@@ -120,7 +120,7 @@ func (service *Service) StoreInstanceID(ID string) error {
 func (service *Service) getKey(key string) ([]byte, error) {
 	var data []byte
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		value := bucket.Get([]byte(key))
@@ -142,7 +142,7 @@ func (service *Service) getKey(key string) ([]byte, error) {
 }
 
 func (service *Service) setKey(key string, value string) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		data := []byte(value)
diff --git a/api/bolt/webhook/webhook.go b/api/bolt/webhook/webhook.go
index d18900de9..d7514f3e7 100644
--- a/api/bolt/webhook/webhook.go
+++ b/api/bolt/webhook/webhook.go
@@ -1,7 +1,7 @@
 package webhook
 
 import (
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/errors"
 	"github.com/portainer/portainer/api/bolt/internal"
 
@@ -15,18 +15,18 @@ const (
 
 // Service represents a service for managing webhook data.
 type Service struct {
-	db *bolt.DB
+	connection *internal.DbConnection
 }
 
 // NewService creates a new instance of a service.
-func NewService(db *bolt.DB) (*Service, error) {
-	err := internal.CreateBucket(db, BucketName)
+func NewService(connection *internal.DbConnection) (*Service, error) {
+	err := internal.CreateBucket(connection, BucketName)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Service{
-		db: db,
+		connection: connection,
 	}, nil
 }
 
@@ -34,7 +34,7 @@ func NewService(db *bolt.DB) (*Service, error) {
 func (service *Service) Webhooks() ([]portainer.Webhook, error) {
 	var webhooks = make([]portainer.Webhook, 0)
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		cursor := bucket.Cursor()
@@ -58,7 +58,7 @@ func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, err
 	var webhook portainer.Webhook
 	identifier := internal.Itob(int(ID))
 
-	err := internal.GetObject(service.db, BucketName, identifier, &webhook)
+	err := internal.GetObject(service.connection, BucketName, identifier, &webhook)
 	if err != nil {
 		return nil, err
 	}
@@ -70,7 +70,7 @@ func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, err
 func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
 	var webhook *portainer.Webhook
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 		cursor := bucket.Cursor()
 
@@ -101,7 +101,7 @@ func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, erro
 func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
 	var webhook *portainer.Webhook
 
-	err := service.db.View(func(tx *bolt.Tx) error {
+	err := service.connection.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 		cursor := bucket.Cursor()
 
@@ -131,12 +131,12 @@ func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error)
 // DeleteWebhook deletes a webhook.
 func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
 	identifier := internal.Itob(int(ID))
-	return internal.DeleteObject(service.db, BucketName, identifier)
+	return internal.DeleteObject(service.connection, BucketName, identifier)
 }
 
 // CreateWebhook assign an ID to a new webhook and saves it.
 func (service *Service) CreateWebhook(webhook *portainer.Webhook) error {
-	return service.db.Update(func(tx *bolt.Tx) error {
+	return service.connection.Update(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(BucketName))
 
 		id, _ := bucket.NextSequence()
diff --git a/api/chisel/service.go b/api/chisel/service.go
index e66983222..d5787d9e5 100644
--- a/api/chisel/service.go
+++ b/api/chisel/service.go
@@ -1,6 +1,7 @@
 package chisel
 
 import (
+	"context"
 	"fmt"
 	"log"
 	"strconv"
@@ -9,7 +10,7 @@ import (
 	"github.com/dchest/uniuri"
 	chserver "github.com/jpillora/chisel/server"
 	cmap "github.com/orcaman/concurrent-map"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt/errors"
 )
 
@@ -29,13 +30,15 @@ type Service struct {
 	dataStore         portainer.DataStore
 	snapshotService   portainer.SnapshotService
 	chiselServer      *chserver.Server
+	shutdownCtx       context.Context
 }
 
 // NewService returns a pointer to a new instance of Service
-func NewService(dataStore portainer.DataStore) *Service {
+func NewService(dataStore portainer.DataStore, shutdownCtx context.Context) *Service {
 	return &Service{
 		tunnelDetailsMap: cmap.New(),
 		dataStore:        dataStore,
+		shutdownCtx:      shutdownCtx,
 	}
 }
 
@@ -83,6 +86,11 @@ func (service *Service) StartTunnelServer(addr, port string, snapshotService por
 	return nil
 }
 
+// StopTunnelServer stops tunnel http server
+func (service *Service) StopTunnelServer() error {
+	return service.chiselServer.Close()
+}
+
 func (service *Service) retrievePrivateKeySeed() (string, error) {
 	var serverInfo *portainer.TunnelServerInfo
 
@@ -108,13 +116,16 @@ func (service *Service) retrievePrivateKeySeed() (string, error) {
 func (service *Service) startTunnelVerificationLoop() {
 	log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
 	ticker := time.NewTicker(tunnelCleanupInterval)
-	stopSignal := make(chan struct{})
 
 	for {
 		select {
 		case <-ticker.C:
 			service.checkTunnels()
-		case <-stopSignal:
+		case <-service.shutdownCtx.Done():
+			log.Println("[DEBUG] Shutting down tunnel service")
+			if err := service.StopTunnelServer(); err != nil {
+				log.Printf("Stopped tunnel service: %s", err)
+			}
 			ticker.Stop()
 			return
 		}
diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go
index f10100c25..6ae3368d6 100644
--- a/api/cmd/portainer/main.go
+++ b/api/cmd/portainer/main.go
@@ -1,10 +1,10 @@
 package main
 
 import (
+	"context"
 	"log"
 	"os"
 	"strings"
-	"time"
 
 	portainer "github.com/portainer/portainer/api"
 	"github.com/portainer/portainer/api/bolt"
@@ -20,6 +20,7 @@ import (
 	"github.com/portainer/portainer/api/http/client"
 	"github.com/portainer/portainer/api/http/proxy"
 	kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
+	"github.com/portainer/portainer/api/internal/edge"
 	"github.com/portainer/portainer/api/internal/snapshot"
 	"github.com/portainer/portainer/api/jwt"
 	"github.com/portainer/portainer/api/kubernetes"
@@ -67,7 +68,7 @@ func initDataStore(dataStorePath string, fileService portainer.FileService) port
 		log.Fatalf("failed initializing data store: %v", err)
 	}
 
-	err = store.MigrateData()
+	err = store.MigrateData(false)
 	if err != nil {
 		log.Fatalf("failed migration: %v", err)
 	}
@@ -136,11 +137,11 @@ func initKubernetesClientFactory(signatureService portainer.DigitalSignatureServ
 	return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID)
 }
 
-func initSnapshotService(snapshotInterval string, dataStore portainer.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory) (portainer.SnapshotService, error) {
+func initSnapshotService(snapshotInterval string, dataStore portainer.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory, shutdownCtx context.Context) (portainer.SnapshotService, error) {
 	dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
 	kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
 
-	snapshotService, err := snapshot.NewService(snapshotInterval, dataStore, dockerSnapshotter, kubernetesSnapshotter)
+	snapshotService, err := snapshot.NewService(snapshotInterval, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx)
 	if err != nil {
 		return nil, err
 	}
@@ -148,21 +149,6 @@ func initSnapshotService(snapshotInterval string, dataStore portainer.DataStore,
 	return snapshotService, nil
 }
 
-func loadEdgeJobsFromDatabase(dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService) error {
-	edgeJobs, err := dataStore.EdgeJob().EdgeJobs()
-	if err != nil {
-		return err
-	}
-
-	for _, edgeJob := range edgeJobs {
-		for endpointID := range edgeJob.Endpoints {
-			reverseTunnelService.AddEdgeJob(endpointID, &edgeJob)
-		}
-	}
-
-	return nil
-}
-
 func initStatus(flags *portainer.CLIFlags) *portainer.Status {
 	return &portainer.Status{
 		Version: portainer.APIVersion,
@@ -353,28 +339,12 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore portainer.DataStore, snap
 	return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
 }
 
-func terminateIfNoAdminCreated(dataStore portainer.DataStore) {
-	timer1 := time.NewTimer(5 * time.Minute)
-	<-timer1.C
-
-	users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
-	if err != nil {
-		log.Fatalf("failed getting admin user: %v", err)
-	}
-
-	if len(users) == 0 {
-		log.Fatal("No administrator account was created after 5 min. Shutting down the Portainer instance for security reasons.")
-		return
-	}
-}
-
-func main() {
-	flags := initCLI()
+func buildServer(flags *portainer.CLIFlags) portainer.Server {
+	shutdownCtx, shutdownTrigger := context.WithCancel(context.Background())
 
 	fileService := initFileService(*flags.Data)
 
 	dataStore := initDataStore(*flags.Data, fileService)
-	defer dataStore.Close()
 
 	if err := dataStore.CheckCurrentEdition(); err != nil {
 		log.Fatal(err)
@@ -400,7 +370,7 @@ func main() {
 		log.Fatalf("failed initializing key pai: %v", err)
 	}
 
-	reverseTunnelService := chisel.NewService(dataStore)
+	reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
 
 	instanceID, err := dataStore.Version().InstanceID()
 	if err != nil {
@@ -410,7 +380,7 @@ func main() {
 	dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
 	kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID)
 
-	snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory)
+	snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
 	if err != nil {
 		log.Fatalf("failed initializing snapshot service: %v", err)
 	}
@@ -434,7 +404,7 @@ func main() {
 		}
 	}
 
-	err = loadEdgeJobsFromDatabase(dataStore, reverseTunnelService)
+	err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
 	if err != nil {
 		log.Fatalf("failed loading edge jobs from database: %v", err)
 	}
@@ -482,14 +452,12 @@ func main() {
 		}
 	}
 
-	go terminateIfNoAdminCreated(dataStore)
-
 	err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
 	if err != nil {
-		log.Fatalf("failed starting tunnel server: %v", err)
+		log.Fatalf("failed starting license service: %s", err)
 	}
 
-	var server portainer.Server = &http.Server{
+	return &http.Server{
 		ReverseTunnelService:        reverseTunnelService,
 		Status:                      applicationStatus,
 		BindAddress:                 *flags.Addr,
@@ -513,11 +481,18 @@ func main() {
 		SSLKey:                      *flags.SSLKey,
 		DockerClientFactory:         dockerClientFactory,
 		KubernetesClientFactory:     kubernetesClientFactory,
-	}
-
-	log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
-	err = server.Start()
-	if err != nil {
-		log.Fatalf("failed starting server: %v", err)
+		ShutdownCtx:                 shutdownCtx,
+		ShutdownTrigger:             shutdownTrigger,
+	}
+}
+
+func main() {
+	flags := initCLI()
+
+	for {
+		server := buildServer(flags)
+		log.Printf("Starting Portainer %s on %s\n", portainer.APIVersion, *flags.Addr)
+		err := server.Start()
+		log.Printf("Http server exited: %s\n", err)
 	}
 }
diff --git a/api/crypto/aes.go b/api/crypto/aes.go
new file mode 100644
index 000000000..5a6f58fe7
--- /dev/null
+++ b/api/crypto/aes.go
@@ -0,0 +1,70 @@
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"io"
+
+	"golang.org/x/crypto/scrypt"
+)
+
+// NOTE: has to go with what is considered to be a simplistic in that it omits any
+// authentication of the encrypted data.
+// Person with better knowledge is welcomed to improve it.
+// sourced from https://golang.org/src/crypto/cipher/example_test.go
+
+var emptySalt []byte = make([]byte, 0, 0)
+
+// AesEncrypt reads from input, encrypts with AES-256 and writes to the output.
+// passphrase is used to generate an encryption key.
+func AesEncrypt(input io.Reader, output io.Writer, passphrase []byte) error {
+	// making a 32 bytes key that would correspond to AES-256
+	// don't necessarily need a salt, so just kept in empty
+	key, err := scrypt.Key(passphrase, emptySalt, 32768, 8, 1, 32)
+	if err != nil {
+		return err
+	}
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return err
+	}
+
+	// If the key is unique for each ciphertext, then it's ok to use a zero
+	// IV.
+	var iv [aes.BlockSize]byte
+	stream := cipher.NewOFB(block, iv[:])
+
+	writer := &cipher.StreamWriter{S: stream, W: output}
+	// Copy the input to the output, encrypting as we go.
+	if _, err := io.Copy(writer, input); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// AesDecrypt reads from input, decrypts with AES-256 and returns the reader to a read decrypted content from.
+// passphrase is used to generate an encryption key.
+func AesDecrypt(input io.Reader, passphrase []byte) (io.Reader, error) {
+	// making a 32 bytes key that would correspond to AES-256
+	// don't necessarily need a salt, so just kept in empty
+	key, err := scrypt.Key(passphrase, emptySalt, 32768, 8, 1, 32)
+	if err != nil {
+		return nil, err
+	}
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	// If the key is unique for each ciphertext, then it's ok to use a zero
+	// IV.
+	var iv [aes.BlockSize]byte
+	stream := cipher.NewOFB(block, iv[:])
+
+	reader := &cipher.StreamReader{S: stream, R: input}
+
+	return reader, nil
+}
diff --git a/api/crypto/aes_test.go b/api/crypto/aes_test.go
new file mode 100644
index 000000000..1a2e377ac
--- /dev/null
+++ b/api/crypto/aes_test.go
@@ -0,0 +1,132 @@
+package crypto
+
+import (
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "encrypt")
+	defer os.RemoveAll(tmpdir)
+
+	var (
+		originFilePath    = filepath.Join(tmpdir, "origin")
+		encryptedFilePath = filepath.Join(tmpdir, "encrypted")
+		decryptedFilePath = filepath.Join(tmpdir, "decrypted")
+	)
+
+	content := []byte("content")
+	ioutil.WriteFile(originFilePath, content, 0600)
+
+	originFile, _ := os.Open(originFilePath)
+	defer originFile.Close()
+
+	encryptedFileWriter, _ := os.Create(encryptedFilePath)
+	defer encryptedFileWriter.Close()
+
+	err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
+	assert.Nil(t, err, "Failed to encrypt a file")
+	encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
+	assert.Nil(t, err, "Couldn't read encrypted file")
+	assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
+
+	encryptedFileReader, _ := os.Open(encryptedFilePath)
+	defer encryptedFileReader.Close()
+
+	decryptedFileWriter, _ := os.Create(decryptedFilePath)
+	defer decryptedFileWriter.Close()
+
+	decryptedReader, err := AesDecrypt(encryptedFileReader, []byte("passphrase"))
+	assert.Nil(t, err, "Failed to decrypt file")
+
+	io.Copy(decryptedFileWriter, decryptedReader)
+
+	decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
+	assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
+}
+
+func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "encrypt")
+	defer os.RemoveAll(tmpdir)
+
+	var (
+		originFilePath    = filepath.Join(tmpdir, "origin")
+		encryptedFilePath = filepath.Join(tmpdir, "encrypted")
+		decryptedFilePath = filepath.Join(tmpdir, "decrypted")
+	)
+
+	content := []byte("content")
+	ioutil.WriteFile(originFilePath, content, 0600)
+
+	originFile, _ := os.Open(originFilePath)
+	defer originFile.Close()
+
+	encryptedFileWriter, _ := os.Create(encryptedFilePath)
+	defer encryptedFileWriter.Close()
+
+	err := AesEncrypt(originFile, encryptedFileWriter, []byte(""))
+	assert.Nil(t, err, "Failed to encrypt a file")
+	encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
+	assert.Nil(t, err, "Couldn't read encrypted file")
+	assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
+
+	encryptedFileReader, _ := os.Open(encryptedFilePath)
+	defer encryptedFileReader.Close()
+
+	decryptedFileWriter, _ := os.Create(decryptedFilePath)
+	defer decryptedFileWriter.Close()
+
+	decryptedReader, err := AesDecrypt(encryptedFileReader, []byte(""))
+	assert.Nil(t, err, "Failed to decrypt file")
+
+	io.Copy(decryptedFileWriter, decryptedReader)
+
+	decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
+	assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
+}
+
+func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T) {
+	tmpdir, _ := ioutils.TempDir("", "encrypt")
+	defer os.RemoveAll(tmpdir)
+
+	var (
+		originFilePath    = filepath.Join(tmpdir, "origin")
+		encryptedFilePath = filepath.Join(tmpdir, "encrypted")
+		decryptedFilePath = filepath.Join(tmpdir, "decrypted")
+	)
+
+	content := []byte("content")
+	ioutil.WriteFile(originFilePath, content, 0600)
+
+	originFile, _ := os.Open(originFilePath)
+	defer originFile.Close()
+
+	encryptedFileWriter, _ := os.Create(encryptedFilePath)
+	defer encryptedFileWriter.Close()
+
+	err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
+	assert.Nil(t, err, "Failed to encrypt a file")
+	encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
+	assert.Nil(t, err, "Couldn't read encrypted file")
+	assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
+
+	encryptedFileReader, _ := os.Open(encryptedFilePath)
+	defer encryptedFileReader.Close()
+
+	decryptedFileWriter, _ := os.Create(decryptedFilePath)
+	defer decryptedFileWriter.Close()
+
+	decryptedReader, err := AesDecrypt(encryptedFileReader, []byte("garbage"))
+	assert.Nil(t, err, "Should allow to decrypt with wrong passphrase")
+
+	io.Copy(decryptedFileWriter, decryptedReader)
+
+	decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
+	assert.NotEqual(t, content, decryptedContent, "Original and decrypted content should NOT match")
+}
diff --git a/api/filesystem/filesystem.go b/api/filesystem/filesystem.go
index 5a766f7cc..62dac019d 100644
--- a/api/filesystem/filesystem.go
+++ b/api/filesystem/filesystem.go
@@ -9,7 +9,7 @@ import (
 	"io/ioutil"
 
 	"github.com/gofrs/uuid"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 
 	"io"
 	"os"
@@ -505,3 +505,8 @@ func (service *Service) GetTemporaryPath() (string, error) {
 
 	return path.Join(service.fileStorePath, TempPath, uid.String()), nil
 }
+
+// GetDataStorePath returns path to data folder
+func (service *Service) GetDatastorePath() string {
+	return service.dataStorePath
+}
diff --git a/api/go.mod b/api/go.mod
index f8cca5616..0b9a01bd7 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -25,6 +25,7 @@ require (
 	github.com/mattn/go-shellwords v1.0.6 // indirect
 	github.com/mitchellh/mapstructure v1.1.2 // indirect
 	github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
+	github.com/pkg/errors v0.9.1
 	github.com/portainer/libcompose v0.5.3
 	github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2
 	github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
diff --git a/api/go.sum b/api/go.sum
index 237ef4cd2..6c69c89d4 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -223,6 +223,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/api/http/handler/backup/backup.go b/api/http/handler/backup/backup.go
new file mode 100644
index 000000000..018dd2d34
--- /dev/null
+++ b/api/http/handler/backup/backup.go
@@ -0,0 +1,53 @@
+package backup
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"path/filepath"
+
+	httperror "github.com/portainer/libhttp/error"
+	"github.com/portainer/libhttp/request"
+	operations "github.com/portainer/portainer/api/backup"
+)
+
+type (
+	backupPayload struct {
+		Password string
+	}
+)
+
+func (p *backupPayload) Validate(r *http.Request) error {
+	return nil
+}
+
+// @id Backup
+// @summary Creates an archive with a system data snapshot that could be used to restore the system.
+// @description  Creates an archive with a system data snapshot that could be used to restore the system.
+// @description **Access policy**: admin
+// @tags backup
+// @security jwt
+// @produce octet-stream
+// @param Password body string false "Password to encrypt the backup with"
+// @success 200 "Success"
+// @failure 400 "Invalid request"
+// @failure 500 "Server error"
+// @router /backup [post]
+func (h *Handler) backup(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+	var payload backupPayload
+	err := request.DecodeAndValidateJSONPayload(r, &payload)
+	if err != nil {
+		return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
+	}
+
+	archivePath, err := operations.CreateBackupArchive(payload.Password, h.gate, h.dataStore, h.filestorePath)
+	if err != nil {
+		return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to create backup", Err: err}
+	}
+	defer os.RemoveAll(filepath.Dir(archivePath))
+
+	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fmt.Sprintf("portainer-backup_%s", filepath.Base(archivePath))))
+	http.ServeFile(w, r, archivePath)
+
+	return nil
+}
diff --git a/api/http/handler/backup/backup_test.go b/api/http/handler/backup/backup_test.go
new file mode 100644
index 000000000..40c7a01bc
--- /dev/null
+++ b/api/http/handler/backup/backup_test.go
@@ -0,0 +1,122 @@
+package backup
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/portainer/portainer/api/adminmonitor"
+	"github.com/portainer/portainer/api/crypto"
+	"github.com/portainer/portainer/api/http/offlinegate"
+	i "github.com/portainer/portainer/api/internal/testhelpers"
+	"github.com/stretchr/testify/assert"
+)
+
+func listFiles(dir string) []string {
+	items := make([]string, 0)
+	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if path == dir {
+			return nil
+		}
+		items = append(items, path)
+		return nil
+	})
+
+	return items
+}
+
+func contains(t *testing.T, list []string, path string) {
+	assert.Contains(t, list, path)
+	copyContent, _ := ioutil.ReadFile(path)
+	assert.Equal(t, "content\n", string(copyContent))
+}
+
+func Test_backupHandlerWithoutPassword_shouldCreateATarballArchive(t *testing.T) {
+	r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"password":""}`))
+	w := httptest.NewRecorder()
+
+	gate := offlinegate.NewOfflineGate()
+	adminMonitor := adminmonitor.New(time.Hour, nil, context.Background())
+
+	handlerErr := NewHandler(nil, i.NewDatastore(), gate, "./test_assets/handler_test", func() {}, adminMonitor).backup(w, r)
+	assert.Nil(t, handlerErr, "Handler should not fail")
+
+	response := w.Result()
+	body, _ := io.ReadAll(response.Body)
+
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	archivePath := filepath.Join(tmpdir, "archive.tar.gz")
+	err := ioutil.WriteFile(archivePath, body, 0600)
+	if err != nil {
+		t.Fatal("Failed to save downloaded .tar.gz archive: ", err)
+	}
+	cmd := exec.Command("tar", "-xzf", archivePath, "-C", tmpdir)
+	err = cmd.Run()
+	if err != nil {
+		t.Fatal("Failed to extract archive: ", err)
+	}
+
+	createdFiles := listFiles(tmpdir)
+
+	contains(t, createdFiles, path.Join(tmpdir, "portainer.key"))
+	contains(t, createdFiles, path.Join(tmpdir, "portainer.pub"))
+	contains(t, createdFiles, path.Join(tmpdir, "tls", "file1"))
+	contains(t, createdFiles, path.Join(tmpdir, "tls", "file2"))
+	assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_file"))
+	assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_folder", "file1"))
+}
+
+func Test_backupHandlerWithPassword_shouldCreateEncryptedATarballArchive(t *testing.T) {
+	r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"password":"secret"}`))
+	w := httptest.NewRecorder()
+
+	gate := offlinegate.NewOfflineGate()
+	adminMonitor := adminmonitor.New(time.Hour, nil, nil)
+
+	handlerErr := NewHandler(nil, i.NewDatastore(), gate, "./test_assets/handler_test", func() {}, adminMonitor).backup(w, r)
+	assert.Nil(t, handlerErr, "Handler should not fail")
+
+	response := w.Result()
+	body, _ := io.ReadAll(response.Body)
+
+	tmpdir, _ := ioutils.TempDir("", "backup")
+	defer os.RemoveAll(tmpdir)
+
+	dr, err := crypto.AesDecrypt(bytes.NewReader(body), []byte("secret"))
+	if err != nil {
+		t.Fatal("Failed to decrypt archive")
+	}
+
+	archivePath := filepath.Join(tmpdir, "archive.tag.gz")
+	archive, _ := os.Create(archivePath)
+	defer archive.Close()
+	io.Copy(archive, dr)
+
+	cmd := exec.Command("tar", "-xzf", archivePath, "-C", tmpdir)
+	err = cmd.Run()
+	if err != nil {
+		t.Fatal("Failed to extract archive: ", err)
+	}
+
+	createdFiles := listFiles(tmpdir)
+
+	contains(t, createdFiles, path.Join(tmpdir, "portainer.key"))
+	contains(t, createdFiles, path.Join(tmpdir, "portainer.pub"))
+	contains(t, createdFiles, path.Join(tmpdir, "tls", "file1"))
+	contains(t, createdFiles, path.Join(tmpdir, "tls", "file2"))
+	assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_file"))
+	assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_folder", "file1"))
+}
diff --git a/api/http/handler/backup/handler.go b/api/http/handler/backup/handler.go
new file mode 100644
index 000000000..489634675
--- /dev/null
+++ b/api/http/handler/backup/handler.go
@@ -0,0 +1,65 @@
+package backup
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/gorilla/mux"
+	httperror "github.com/portainer/libhttp/error"
+	portainer "github.com/portainer/portainer/api"
+	"github.com/portainer/portainer/api/adminmonitor"
+	"github.com/portainer/portainer/api/http/offlinegate"
+	"github.com/portainer/portainer/api/http/security"
+)
+
+// Handler is an http handler responsible for backup and restore portainer state
+type Handler struct {
+	*mux.Router
+	bouncer         *security.RequestBouncer
+	dataStore       portainer.DataStore
+	gate            *offlinegate.OfflineGate
+	filestorePath   string
+	shutdownTrigger context.CancelFunc
+	adminMonitor    *adminmonitor.Monitor
+}
+
+// NewHandler creates an new instance of backup handler
+func NewHandler(bouncer *security.RequestBouncer, dataStore portainer.DataStore, gate *offlinegate.OfflineGate, filestorePath string, shutdownTrigger context.CancelFunc, adminMonitor *adminmonitor.Monitor) *Handler {
+	h := &Handler{
+		Router:          mux.NewRouter(),
+		bouncer:         bouncer,
+		dataStore:       dataStore,
+		gate:            gate,
+		filestorePath:   filestorePath,
+		shutdownTrigger: shutdownTrigger,
+		adminMonitor:    adminMonitor,
+	}
+
+	h.Handle("/backup", bouncer.RestrictedAccess(adminAccess(httperror.LoggerHandler(h.backup)))).Methods(http.MethodPost)
+	h.Handle("/restore", bouncer.PublicAccess(httperror.LoggerHandler(h.restore))).Methods(http.MethodPost)
+
+	return h
+}
+
+func adminAccess(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		securityContext, err := security.RetrieveRestrictedRequestContext(r)
+		if err != nil {
+			httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user info from request context", err)
+		}
+
+		if !securityContext.IsAdmin {
+			httperror.WriteError(w, http.StatusUnauthorized, "User is not authorized to perfom the action", nil)
+		}
+
+		next.ServeHTTP(w, r)
+	})
+}
+
+func systemWasInitialized(dataStore portainer.DataStore) (bool, error) {
+	users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
+	if err != nil {
+		return false, err
+	}
+	return len(users) > 0, nil
+}
diff --git a/api/http/handler/backup/restore.go b/api/http/handler/backup/restore.go
new file mode 100644
index 000000000..db08e5335
--- /dev/null
+++ b/api/http/handler/backup/restore.go
@@ -0,0 +1,69 @@
+package backup
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+
+	"github.com/pkg/errors"
+	httperror "github.com/portainer/libhttp/error"
+	"github.com/portainer/libhttp/request"
+	operations "github.com/portainer/portainer/api/backup"
+)
+
+type restorePayload struct {
+	FileContent []byte
+	FileName    string
+	Password    string
+}
+
+// @id Restore
+// @summary Triggers a system restore using provided backup file
+// @description Triggers a system restore using provided backup file
+// @description **Access policy**: public
+// @tags backup
+// @param FileContent body []byte true "Content of the backup"
+// @param FileName body string true "File name"
+// @param Password body string false "Password to decrypt the backup with"
+// @success 200  "Success"
+// @failure 400 "Invalid request"
+// @failure 500 "Server error"
+// @router /restore [post]
+func (h *Handler) restore(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+	initialized, err := h.adminMonitor.WasInitialized()
+	if err != nil {
+		return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to check system initialization", Err: err}
+	}
+	if initialized {
+		return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Cannot restore already initialized instance", Err: errors.New("system already initialized")}
+	}
+	h.adminMonitor.Stop()
+	defer h.adminMonitor.Start()
+
+	var payload restorePayload
+	err = decodeForm(r, &payload)
+	if err != nil {
+		return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
+	}
+
+	var archiveReader io.Reader = bytes.NewReader(payload.FileContent)
+	err = operations.RestoreArchive(archiveReader, payload.Password, h.filestorePath, h.gate, h.dataStore, h.shutdownTrigger)
+	if err != nil {
+		return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to restore the backup", Err: err}
+	}
+
+	return nil
+}
+
+func decodeForm(r *http.Request, p *restorePayload) error {
+	content, name, err := request.RetrieveMultiPartFormFile(r, "file")
+	if err != nil {
+		return err
+	}
+	p.FileContent = content
+	p.FileName = name
+
+	password, _ := request.RetrieveMultiPartFormValue(r, "password", true)
+	p.Password = password
+	return nil
+}
diff --git a/api/http/handler/backup/restore_test.go b/api/http/handler/backup/restore_test.go
new file mode 100644
index 000000000..bf9617248
--- /dev/null
+++ b/api/http/handler/backup/restore_test.go
@@ -0,0 +1,123 @@
+package backup
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+
+	portainer "github.com/portainer/portainer/api"
+	"github.com/portainer/portainer/api/adminmonitor"
+	"github.com/portainer/portainer/api/http/offlinegate"
+	i "github.com/portainer/portainer/api/internal/testhelpers"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_restoreArchive_usingCombinationOfPasswords(t *testing.T) {
+	tests := []struct {
+		name            string
+		backupPassword  string
+		restorePassword string
+		fails           bool
+	}{
+		{
+			name:            "empty password to both encrypt and decrypt",
+			backupPassword:  "",
+			restorePassword: "",
+			fails:           false,
+		},
+		{
+			name:            "same password to encrypt and decrypt",
+			backupPassword:  "secret",
+			restorePassword: "secret",
+			fails:           false,
+		},
+		{
+			name:            "different passwords to encrypt and decrypt",
+			backupPassword:  "secret",
+			restorePassword: "terces",
+			fails:           true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			datastore := i.NewDatastore(i.WithUsers([]portainer.User{}), i.WithEdgeJobs([]portainer.EdgeJob{}))
+			adminMonitor := adminmonitor.New(time.Hour, datastore, context.Background())
+
+			h := NewHandler(nil, datastore, offlinegate.NewOfflineGate(), "./test_assets/handler_test", func() {}, adminMonitor)
+
+			//backup
+			archive := backup(t, h, test.backupPassword)
+
+			//restore
+			w := httptest.NewRecorder()
+			r, err := prepareMultipartRequest(test.restorePassword, archive)
+			assert.Nil(t, err, "Shouldn't fail to write multipart form")
+
+			restoreErr := h.restore(w, r)
+			assert.Equal(t, test.fails, restoreErr != nil, "Didn't meet expectation of failing restore handler")
+		})
+	}
+}
+
+func Test_restoreArchive_shouldFailIfSystemWasAlreadyInitialized(t *testing.T) {
+	admin := portainer.User{
+		Role: portainer.AdministratorRole,
+	}
+	datastore := i.NewDatastore(i.WithUsers([]portainer.User{admin}), i.WithEdgeJobs([]portainer.EdgeJob{}))
+	adminMonitor := adminmonitor.New(time.Hour, datastore, context.Background())
+
+	h := NewHandler(nil, datastore, offlinegate.NewOfflineGate(), "./test_assets/handler_test", func() {}, adminMonitor)
+
+	//backup
+	archive := backup(t, h, "password")
+
+	//restore
+	w := httptest.NewRecorder()
+	r, err := prepareMultipartRequest("password", archive)
+	assert.Nil(t, err, "Shouldn't fail to write multipart form")
+
+	restoreErr := h.restore(w, r)
+	assert.NotNil(t, restoreErr, "Should fail, because system it already initialized")
+	assert.Equal(t, "Cannot restore already initialized instance", restoreErr.Message, "Should fail with certain error")
+}
+
+func backup(t *testing.T, h *Handler, password string) []byte {
+	r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(fmt.Sprintf(`{"password":"%s"}`, password)))
+	w := httptest.NewRecorder()
+
+	backupErr := h.backup(w, r)
+	assert.Nil(t, backupErr, "Backup should not fail")
+
+	response := w.Result()
+	archive, _ := io.ReadAll(response.Body)
+	return archive
+}
+
+func prepareMultipartRequest(password string, file []byte) (*http.Request, error) {
+	var body bytes.Buffer
+	w := multipart.NewWriter(&body)
+	err := w.WriteField("password", password)
+	if err != nil {
+		return nil, err
+	}
+	fw, err := w.CreateFormFile("file", "filename")
+	if err != nil {
+		return nil, err
+	}
+	io.Copy(fw, bytes.NewReader(file))
+
+	r := httptest.NewRequest(http.MethodPost, "http://localhost/", &body)
+	r.Header.Set("Content-Type", w.FormDataContentType())
+
+	w.Close()
+
+	return r, nil
+}
diff --git a/api/http/handler/backup/test_assets/handler_test/extra_file b/api/http/handler/backup/test_assets/handler_test/extra_file
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/http/handler/backup/test_assets/handler_test/extra_file
@@ -0,0 +1 @@
+content
diff --git a/api/http/handler/backup/test_assets/handler_test/extra_folder/file1 b/api/http/handler/backup/test_assets/handler_test/extra_folder/file1
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/http/handler/backup/test_assets/handler_test/extra_folder/file1
@@ -0,0 +1 @@
+content
diff --git a/api/http/handler/backup/test_assets/handler_test/portainer.db b/api/http/handler/backup/test_assets/handler_test/portainer.db
new file mode 100644
index 000000000..e69de29bb
diff --git a/api/http/handler/backup/test_assets/handler_test/portainer.key b/api/http/handler/backup/test_assets/handler_test/portainer.key
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/http/handler/backup/test_assets/handler_test/portainer.key
@@ -0,0 +1 @@
+content
diff --git a/api/http/handler/backup/test_assets/handler_test/portainer.pub b/api/http/handler/backup/test_assets/handler_test/portainer.pub
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/http/handler/backup/test_assets/handler_test/portainer.pub
@@ -0,0 +1 @@
+content
diff --git a/api/http/handler/backup/test_assets/handler_test/tls/file1 b/api/http/handler/backup/test_assets/handler_test/tls/file1
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/http/handler/backup/test_assets/handler_test/tls/file1
@@ -0,0 +1 @@
+content
diff --git a/api/http/handler/backup/test_assets/handler_test/tls/file2 b/api/http/handler/backup/test_assets/handler_test/tls/file2
new file mode 100644
index 000000000..d95f3ad14
--- /dev/null
+++ b/api/http/handler/backup/test_assets/handler_test/tls/file2
@@ -0,0 +1 @@
+content
diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go
index 05f693aa3..2942c3a17 100644
--- a/api/http/handler/handler.go
+++ b/api/http/handler/handler.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/portainer/portainer/api/http/handler/auth"
+	"github.com/portainer/portainer/api/http/handler/backup"
 	"github.com/portainer/portainer/api/http/handler/customtemplates"
 	"github.com/portainer/portainer/api/http/handler/dockerhub"
 	"github.com/portainer/portainer/api/http/handler/edgegroups"
@@ -36,6 +37,7 @@ import (
 // Handler is a collection of all the service handlers.
 type Handler struct {
 	AuthHandler            *auth.Handler
+	BackupHandler          *backup.Handler
 	CustomTemplatesHandler *customtemplates.Handler
 	DockerHubHandler       *dockerhub.Handler
 	EdgeGroupsHandler      *edgegroups.Handler
@@ -140,6 +142,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	switch {
 	case strings.HasPrefix(r.URL.Path, "/api/auth"):
 		http.StripPrefix("/api", h.AuthHandler).ServeHTTP(w, r)
+	case strings.HasPrefix(r.URL.Path, "/api/backup"):
+		http.StripPrefix("/api", h.BackupHandler).ServeHTTP(w, r)
+	case strings.HasPrefix(r.URL.Path, "/api/restore"):
+		http.StripPrefix("/api", h.BackupHandler).ServeHTTP(w, r)
 	case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
 		http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
 	case strings.HasPrefix(r.URL.Path, "/api/custom_templates"):
diff --git a/api/http/offlinegate/offlinegate.go b/api/http/offlinegate/offlinegate.go
new file mode 100644
index 000000000..e3614f451
--- /dev/null
+++ b/api/http/offlinegate/offlinegate.go
@@ -0,0 +1,71 @@
+package offlinegate
+
+import (
+	"log"
+	"net/http"
+	"sync"
+	"time"
+
+	httperror "github.com/portainer/libhttp/error"
+)
+
+// OfflineGate is a entity that works similar to a mutex with a signaling
+// Only the caller that have Locked an gate can unlock it, otherw will be blocked with a call to Lock.
+// Gate provides a passthrough http middleware that will wait for a locked gate to be unlocked.
+// For a safety reasons, middleware will timeout
+type OfflineGate struct {
+	lock        *sync.Mutex
+	signalingCh chan interface{}
+}
+
+// NewOfflineGate creates a new gate
+func NewOfflineGate() *OfflineGate {
+	return &OfflineGate{
+		lock: &sync.Mutex{},
+	}
+}
+
+// Lock locks readonly gate and returns a function to unlock
+func (o *OfflineGate) Lock() func() {
+	o.lock.Lock()
+	o.signalingCh = make(chan interface{})
+	return o.unlock
+}
+
+func (o *OfflineGate) unlock() {
+	if o.signalingCh == nil {
+		return
+	}
+
+	close(o.signalingCh)
+	o.signalingCh = nil
+	o.lock.Unlock()
+}
+
+// Watch returns a signaling channel.
+// Unless channel is nil, client needs to watch for a signal on a channel to know when gate is unlocked.
+// Signal channel is disposable: onced signaled, has to be disposed and acquired again.
+func (o *OfflineGate) Watch() chan interface{} {
+	return o.signalingCh
+}
+
+// WaitingMiddleware returns an http handler that waits for the gate to be unlocked before continuing
+func (o *OfflineGate) WaitingMiddleware(timeout time.Duration, next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		signalingCh := o.Watch()
+
+		if signalingCh != nil {
+			if r.Method != "GET" && r.Method != "HEAD" && r.Method != "OPTIONS" {
+				select {
+				case <-signalingCh:
+				case <-time.After(timeout):
+					log.Println("error: Timeout waiting for the offline gate to signal")
+					httperror.WriteError(w, http.StatusRequestTimeout, "Timeout waiting for the offline gate to signal", http.ErrHandlerTimeout)
+				}
+			}
+		}
+
+		next.ServeHTTP(w, r)
+
+	})
+}
diff --git a/api/http/offlinegate/offlinegate_test.go b/api/http/offlinegate/offlinegate_test.go
new file mode 100644
index 000000000..c63bf68f8
--- /dev/null
+++ b/api/http/offlinegate/offlinegate_test.go
@@ -0,0 +1,217 @@
+package offlinegate
+
+import (
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_canLockAndUnlock(t *testing.T) {
+	o := NewOfflineGate()
+
+	unlock := o.Lock()
+	unlock()
+}
+
+func Test_hasToBeUnlockedToLockAgain(t *testing.T) {
+	// scenario:
+	// 1. first routine starts and locks the gate
+	// 2. first routine starts a second and wait for the second to start
+	// 3. second start but waits for the gate to be released
+	// 4. first continues and unlocks the gate, when done
+	// 5. second be able to continue
+	// 6. second lock the gate, does the job and unlocks it
+
+	o := NewOfflineGate()
+
+	wg := sync.WaitGroup{}
+	wg.Add(2)
+
+	result := make([]string, 0, 2)
+	go func() {
+		unlock := o.Lock()
+		defer unlock()
+		waitForSecondToStart := sync.WaitGroup{}
+		waitForSecondToStart.Add(1)
+		go func() {
+			waitForSecondToStart.Done()
+			unlock := o.Lock()
+			defer unlock()
+			result = append(result, "second")
+			wg.Done()
+		}()
+		waitForSecondToStart.Wait()
+		result = append(result, "first")
+		wg.Done()
+	}()
+
+	wg.Wait()
+
+	if len(result) != 2 || result[0] != "first" || result[1] != "second" {
+		t.Error("Second call have disresregarded a raised lock")
+	}
+
+}
+
+func Test_waitChannelWillBeEmpty_ifGateIsUnlocked(t *testing.T) {
+	o := NewOfflineGate()
+
+	signalingCh := o.Watch()
+	if signalingCh != nil {
+		t.Error("Signaling channel should be empty")
+	}
+}
+
+func Test_startWaitingForSignal_beforeGateGetsUnlocked(t *testing.T) {
+	// scenario:
+	// 1. main routing locks the gate and waits for a consumer to start up
+	// 2. consumer starts up, notifies main and begins waiting for the gate to be unlocked
+	// 3. main unlocks the gate
+	// 4. consumer be able to continue
+
+	o := NewOfflineGate()
+	unlock := o.Lock()
+
+	signalingCh := o.Watch()
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+	readerIsReady := sync.WaitGroup{}
+	readerIsReady.Add(1)
+
+	go func(t *testing.T) {
+		readerIsReady.Done()
+
+		// either wait for a signal or timeout
+		select {
+		case <-signalingCh:
+		case <-time.After(10 * time.Second):
+			t.Error("Failed to wait for a signal, exit by timeout")
+		}
+		wg.Done()
+	}(t)
+
+	readerIsReady.Wait()
+	unlock()
+
+	wg.Wait()
+}
+
+func Test_startWaitingForSignal_afterGateGetsUnlocked(t *testing.T) {
+	// scenario:
+	// 1. main routing locks, gets waiting channel and unlocks
+	// 2. consumer starts up and begins waiting for the gate to be unlocked
+	// 3. consumer gets signal immediately and continues
+
+	o := NewOfflineGate()
+	unlock := o.Lock()
+	signalingCh := o.Watch()
+	unlock()
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func(t *testing.T) {
+		// either wait for a signal or timeout
+		select {
+		case <-signalingCh:
+		case <-time.After(10 * time.Second):
+			t.Error("Failed to wait for a signal, exit by timeout")
+		}
+		wg.Done()
+	}(t)
+
+	wg.Wait()
+}
+
+func Test_waitingMiddleware_executesImmediately_whenNotLocked(t *testing.T) {
+	// scenario:
+	// 1. create an gate
+	// 2. kick off a waiting middleware that will release immediately as gate wasn't locked
+	// 3. middleware shouldn't timeout
+
+	o := NewOfflineGate()
+
+	request := httptest.NewRequest(http.MethodPost, "/", nil)
+	response := httptest.NewRecorder()
+
+	timeout := 2 * time.Second
+	start := time.Now()
+	o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		elapsed := time.Since(start)
+		if elapsed >= timeout {
+			t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
+		}
+		w.Write([]byte("success"))
+	})).ServeHTTP(response, request)
+
+	body, _ := io.ReadAll(response.Body)
+	if string(body) != "success" {
+		t.Error("Didn't receive expected result from the hanlder")
+	}
+}
+
+func Test_waitingMiddleware_waitsForTheLockToBeReleased(t *testing.T) {
+	// scenario:
+	// 1. create an gate and lock it
+	// 2. kick off a routing that will unlock the gate after 1 second
+	// 3. kick off a waiting middleware that will wait for lock to be eventually released
+	// 4. middleware shouldn't timeout
+
+	o := NewOfflineGate()
+	unlock := o.Lock()
+
+	request := httptest.NewRequest(http.MethodPost, "/", nil)
+	response := httptest.NewRecorder()
+
+	go func() {
+		time.Sleep(1 * time.Second)
+		unlock()
+	}()
+
+	timeout := 10 * time.Second
+	start := time.Now()
+	o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		elapsed := time.Since(start)
+		if elapsed >= timeout {
+			t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
+		}
+		w.Write([]byte("success"))
+	})).ServeHTTP(response, request)
+
+	body, _ := io.ReadAll(response.Body)
+	if string(body) != "success" {
+		t.Error("Didn't receive expected result from the hanlder")
+	}
+}
+
+func Test_waitingMiddleware_mayTimeout_whenLockedForTooLong(t *testing.T) {
+	/*
+		scenario:
+		1. create an gate and lock it
+		2. kick off a waiting middleware that will wait for lock to be eventually released
+		3. because we never unlocked the gate, middleware suppose to timeout
+	*/
+	o := NewOfflineGate()
+	o.Lock()
+
+	request := httptest.NewRequest(http.MethodPost, "/", nil)
+	response := httptest.NewRecorder()
+
+	timeout := 1 * time.Second
+	start := time.Now()
+	o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		elapsed := time.Since(start)
+		if elapsed < timeout {
+			t.Error("WaitingMiddleware suppose to timeout, but it didnt")
+		}
+		w.Write([]byte("success"))
+	})).ServeHTTP(response, request)
+
+	assert.Equal(t, http.StatusRequestTimeout, response.Result().StatusCode, "Request support to timeout waiting for the gate")
+}
diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go
index ac55555d3..5c57314af 100644
--- a/api/http/security/bouncer.go
+++ b/api/http/security/bouncer.go
@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	httperror "github.com/portainer/libhttp/error"
-	"github.com/portainer/portainer/api"
+	portainer "github.com/portainer/portainer/api"
 	bolterrors "github.com/portainer/portainer/api/bolt/errors"
 	httperrors "github.com/portainer/portainer/api/http/errors"
 )
@@ -153,6 +153,9 @@ func (bouncer *RequestBouncer) RegistryAccess(r *http.Request, registry *portain
 	return nil
 }
 
+// handlers are applied backwards to the incoming request:
+// - add secure handlers to the response
+// - parse the JWT token and put it into the http context.
 func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler {
 	h = bouncer.mwCheckAuthentication(h)
 	h = mwSecureHeaders(h)
@@ -216,6 +219,8 @@ func (bouncer *RequestBouncer) mwUpgradeToRestrictedRequest(next http.Handler) h
 }
 
 // mwCheckAuthentication provides Authentication middleware for handlers
+//
+// It parses the JWT token and adds the parsed token data to the http context
 func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		var tokenData *portainer.TokenData
@@ -269,30 +274,31 @@ func mwSecureHeaders(next http.Handler) http.Handler {
 }
 
 func (bouncer *RequestBouncer) newRestrictedContextRequest(userID portainer.UserID, userRole portainer.UserRole) (*RestrictedRequestContext, error) {
-	requestContext := &RestrictedRequestContext{
-		IsAdmin: true,
-		UserID:  userID,
+	if userRole == portainer.AdministratorRole {
+		return &RestrictedRequestContext{
+			IsAdmin: true,
+			UserID:  userID,
+		}, nil
 	}
 
-	if userRole != portainer.AdministratorRole {
-		requestContext.IsAdmin = false
-		memberships, err := bouncer.dataStore.TeamMembership().TeamMembershipsByUserID(userID)
-		if err != nil {
-			return nil, err
-		}
-
-		isTeamLeader := false
-		for _, membership := range memberships {
-			if membership.Role == portainer.TeamLeader {
-				isTeamLeader = true
-			}
-		}
-
-		requestContext.IsTeamLeader = isTeamLeader
-		requestContext.UserMemberships = memberships
+	memberships, err := bouncer.dataStore.TeamMembership().TeamMembershipsByUserID(userID)
+	if err != nil {
+		return nil, err
 	}
 
-	return requestContext, nil
+	isTeamLeader := false
+	for _, membership := range memberships {
+		if membership.Role == portainer.TeamLeader {
+			isTeamLeader = true
+		}
+	}
+
+	return &RestrictedRequestContext{
+		IsAdmin:         false,
+		UserID:          userID,
+		IsTeamLeader:    isTeamLeader,
+		UserMemberships: memberships,
+	}, nil
 }
 
 // EdgeComputeOperation defines a restriced edge compute operation.
diff --git a/api/http/server.go b/api/http/server.go
index ad7826087..4e0e8c775 100644
--- a/api/http/server.go
+++ b/api/http/server.go
@@ -1,15 +1,20 @@
 package http
 
 import (
+	"context"
+	"fmt"
+	"log"
 	"net/http"
 	"path/filepath"
 	"time"
 
 	portainer "github.com/portainer/portainer/api"
+	"github.com/portainer/portainer/api/adminmonitor"
 	"github.com/portainer/portainer/api/crypto"
 	"github.com/portainer/portainer/api/docker"
 	"github.com/portainer/portainer/api/http/handler"
 	"github.com/portainer/portainer/api/http/handler/auth"
+	"github.com/portainer/portainer/api/http/handler/backup"
 	"github.com/portainer/portainer/api/http/handler/customtemplates"
 	"github.com/portainer/portainer/api/http/handler/dockerhub"
 	"github.com/portainer/portainer/api/http/handler/edgegroups"
@@ -36,10 +41,10 @@ import (
 	"github.com/portainer/portainer/api/http/handler/users"
 	"github.com/portainer/portainer/api/http/handler/webhooks"
 	"github.com/portainer/portainer/api/http/handler/websocket"
+	"github.com/portainer/portainer/api/http/offlinegate"
 	"github.com/portainer/portainer/api/http/proxy"
 	"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
 	"github.com/portainer/portainer/api/http/security"
-
 	"github.com/portainer/portainer/api/kubernetes/cli"
 )
 
@@ -69,6 +74,8 @@ type Server struct {
 	DockerClientFactory         *docker.ClientFactory
 	KubernetesClientFactory     *cli.ClientFactory
 	KubernetesDeployer          portainer.KubernetesDeployer
+	ShutdownCtx                 context.Context
+	ShutdownTrigger             context.CancelFunc
 }
 
 // Start starts the HTTP server
@@ -78,6 +85,7 @@ func (server *Server) Start() error {
 	requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService)
 
 	rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
+	offlineGate := offlinegate.NewOfflineGate()
 
 	var authHandler = auth.NewHandler(requestBouncer, rateLimiter)
 	authHandler.DataStore = server.DataStore
@@ -88,6 +96,11 @@ func (server *Server) Start() error {
 	authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
 	authHandler.OAuthService = server.OAuthService
 
+	adminMonitor := adminmonitor.New(5*time.Minute, server.DataStore, server.ShutdownCtx)
+	adminMonitor.Start()
+
+	var backupHandler = backup.NewHandler(requestBouncer, server.DataStore, offlineGate, server.FileService.GetDatastorePath(), server.ShutdownTrigger, adminMonitor)
+
 	var roleHandler = roles.NewHandler(requestBouncer)
 	roleHandler.DataStore = server.DataStore
 
@@ -200,6 +213,7 @@ func (server *Server) Start() error {
 	server.Handler = &handler.Handler{
 		RoleHandler:            roleHandler,
 		AuthHandler:            authHandler,
+		BackupHandler:          backupHandler,
 		CustomTemplatesHandler: customTemplatesHandler,
 		DockerHubHandler:       dockerHubHandler,
 		EdgeGroupsHandler:      edgeGroupsHandler,
@@ -231,10 +245,27 @@ func (server *Server) Start() error {
 		Addr:    server.BindAddress,
 		Handler: server.Handler,
 	}
+	httpServer.Handler = offlineGate.WaitingMiddleware(time.Minute, httpServer.Handler)
 
 	if server.SSL {
 		httpServer.TLSConfig = crypto.CreateServerTLSConfiguration()
 		return httpServer.ListenAndServeTLS(server.SSLCert, server.SSLKey)
 	}
+
+	go server.shutdown(httpServer)
+
 	return httpServer.ListenAndServe()
 }
+
+func (server *Server) shutdown(httpServer *http.Server) {
+	<-server.ShutdownCtx.Done()
+
+	log.Println("[DEBUG] Shutting down http server")
+	shutdownTimeout, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+
+	err := httpServer.Shutdown(shutdownTimeout)
+	if err != nil {
+		fmt.Printf("Failed shutdown http server: %s \n", err)
+	}
+}
diff --git a/api/internal/edge/edgejob.go b/api/internal/edge/edgejob.go
new file mode 100644
index 000000000..bbd55b8e1
--- /dev/null
+++ b/api/internal/edge/edgejob.go
@@ -0,0 +1,19 @@
+package edge
+
+import portainer "github.com/portainer/portainer/api"
+
+// LoadEdgeJobs registers all edge jobs inside corresponding endpoint tunnel
+func LoadEdgeJobs(dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService) error {
+	edgeJobs, err := dataStore.EdgeJob().EdgeJobs()
+	if err != nil {
+		return err
+	}
+
+	for _, edgeJob := range edgeJobs {
+		for endpointID := range edgeJob.Endpoints {
+			reverseTunnelService.AddEdgeJob(endpointID, &edgeJob)
+		}
+	}
+
+	return nil
+}
diff --git a/api/internal/snapshot/snapshot.go b/api/internal/snapshot/snapshot.go
index 2c2e5ef34..31b17acda 100644
--- a/api/internal/snapshot/snapshot.go
+++ b/api/internal/snapshot/snapshot.go
@@ -1,6 +1,7 @@
 package snapshot
 
 import (
+	"context"
 	"log"
 	"time"
 
@@ -16,10 +17,11 @@ type Service struct {
 	snapshotIntervalInSeconds float64
 	dockerSnapshotter         portainer.DockerSnapshotter
 	kubernetesSnapshotter     portainer.KubernetesSnapshotter
+	shutdownCtx               context.Context
 }
 
 // NewService creates a new instance of a service
-func NewService(snapshotInterval string, dataStore portainer.DataStore, dockerSnapshotter portainer.DockerSnapshotter, kubernetesSnapshotter portainer.KubernetesSnapshotter) (*Service, error) {
+func NewService(snapshotInterval string, dataStore portainer.DataStore, dockerSnapshotter portainer.DockerSnapshotter, kubernetesSnapshotter portainer.KubernetesSnapshotter, shutdownCtx context.Context) (*Service, error) {
 	snapshotFrequency, err := time.ParseDuration(snapshotInterval)
 	if err != nil {
 		return nil, err
@@ -30,6 +32,7 @@ func NewService(snapshotInterval string, dataStore portainer.DataStore, dockerSn
 		snapshotIntervalInSeconds: snapshotFrequency.Seconds(),
 		dockerSnapshotter:         dockerSnapshotter,
 		kubernetesSnapshotter:     kubernetesSnapshotter,
+		shutdownCtx:               shutdownCtx,
 	}, nil
 }
 
@@ -43,7 +46,7 @@ func (service *Service) Start() {
 	service.startSnapshotLoop()
 }
 
-func (service *Service) stop() {
+func (service *Service) Stop() {
 	if service.refreshSignal == nil {
 		return
 	}
@@ -55,7 +58,7 @@ func (service *Service) stop() {
 
 // SetSnapshotInterval sets the snapshot interval and resets the service
 func (service *Service) SetSnapshotInterval(snapshotInterval string) error {
-	service.stop()
+	service.Stop()
 
 	snapshotFrequency, err := time.ParseDuration(snapshotInterval)
 	if err != nil {
@@ -132,9 +135,12 @@ func (service *Service) startSnapshotLoop() error {
 				if err != nil {
 					log.Printf("[ERROR] [internal,snapshot] [message: background schedule error (endpoint snapshot).] [error: %s]", err)
 				}
-
+			case <-service.shutdownCtx.Done():
+				log.Println("[DEBUG] [internal,snapshot] [message: shutting down snapshotting]")
+				ticker.Stop()
+				return
 			case <-service.refreshSignal:
-				log.Println("[DEBUG] [internal,snapshot] [message: shutting down Snapshot service]")
+				log.Println("[DEBUG] [internal,snapshot] [message: shutting down snapshotting]")
 				ticker.Stop()
 				return
 			}
diff --git a/api/internal/testhelpers/datastore.go b/api/internal/testhelpers/datastore.go
new file mode 100644
index 000000000..a58da5c7c
--- /dev/null
+++ b/api/internal/testhelpers/datastore.go
@@ -0,0 +1,114 @@
+package testhelpers
+
+import (
+	"io"
+
+	portainer "github.com/portainer/portainer/api"
+)
+
+type datastore struct {
+	dockerHub        portainer.DockerHubService
+	customTemplate   portainer.CustomTemplateService
+	edgeGroup        portainer.EdgeGroupService
+	edgeJob          portainer.EdgeJobService
+	edgeStack        portainer.EdgeStackService
+	endpoint         portainer.EndpointService
+	endpointGroup    portainer.EndpointGroupService
+	endpointRelation portainer.EndpointRelationService
+	registry         portainer.RegistryService
+	resourceControl  portainer.ResourceControlService
+	role             portainer.RoleService
+	settings         portainer.SettingsService
+	stack            portainer.StackService
+	tag              portainer.TagService
+	teamMembership   portainer.TeamMembershipService
+	team             portainer.TeamService
+	tunnelServer     portainer.TunnelServerService
+	user             portainer.UserService
+	version          portainer.VersionService
+	webhook          portainer.WebhookService
+}
+
+func (d *datastore) BackupTo(io.Writer) error                            { return nil }
+func (d *datastore) Open() error                                         { return nil }
+func (d *datastore) Init() error                                         { return nil }
+func (d *datastore) Close() error                                        { return nil }
+func (d *datastore) CheckCurrentEdition() error                          { return nil }
+func (d *datastore) IsNew() bool                                         { return false }
+func (d *datastore) MigrateData(force bool) error                        { return nil }
+func (d *datastore) RollbackToCE() error                                 { return nil }
+func (d *datastore) DockerHub() portainer.DockerHubService               { return d.dockerHub }
+func (d *datastore) CustomTemplate() portainer.CustomTemplateService     { return d.customTemplate }
+func (d *datastore) EdgeGroup() portainer.EdgeGroupService               { return d.edgeGroup }
+func (d *datastore) EdgeJob() portainer.EdgeJobService                   { return d.edgeJob }
+func (d *datastore) EdgeStack() portainer.EdgeStackService               { return d.edgeStack }
+func (d *datastore) Endpoint() portainer.EndpointService                 { return d.endpoint }
+func (d *datastore) EndpointGroup() portainer.EndpointGroupService       { return d.endpointGroup }
+func (d *datastore) EndpointRelation() portainer.EndpointRelationService { return d.endpointRelation }
+func (d *datastore) Registry() portainer.RegistryService                 { return d.registry }
+func (d *datastore) ResourceControl() portainer.ResourceControlService   { return d.resourceControl }
+func (d *datastore) Role() portainer.RoleService                         { return d.role }
+func (d *datastore) Settings() portainer.SettingsService                 { return d.settings }
+func (d *datastore) Stack() portainer.StackService                       { return d.stack }
+func (d *datastore) Tag() portainer.TagService                           { return d.tag }
+func (d *datastore) TeamMembership() portainer.TeamMembershipService     { return d.teamMembership }
+func (d *datastore) Team() portainer.TeamService                         { return d.team }
+func (d *datastore) TunnelServer() portainer.TunnelServerService         { return d.tunnelServer }
+func (d *datastore) User() portainer.UserService                         { return d.user }
+func (d *datastore) Version() portainer.VersionService                   { return d.version }
+func (d *datastore) Webhook() portainer.WebhookService                   { return d.webhook }
+
+type datastoreOption = func(d *datastore)
+
+// NewDatastore creates new instance of datastore.
+// Will apply options before returning, opts will be applied from left to right.
+func NewDatastore(options ...datastoreOption) *datastore {
+	d := datastore{}
+	for _, o := range options {
+		o(&d)
+	}
+	return &d
+}
+
+type stubUserService struct {
+	users []portainer.User
+}
+
+func (s *stubUserService) User(ID portainer.UserID) (*portainer.User, error)       { return nil, nil }
+func (s *stubUserService) UserByUsername(username string) (*portainer.User, error) { return nil, nil }
+func (s *stubUserService) Users() ([]portainer.User, error)                        { return s.users, nil }
+func (s *stubUserService) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
+	return s.users, nil
+}
+func (s *stubUserService) CreateUser(user *portainer.User) error                      { return nil }
+func (s *stubUserService) UpdateUser(ID portainer.UserID, user *portainer.User) error { return nil }
+func (s *stubUserService) DeleteUser(ID portainer.UserID) error                       { return nil }
+
+// WithUsers datastore option that will instruct datastore to return provided users
+func WithUsers(us []portainer.User) datastoreOption {
+	return func(d *datastore) {
+		d.user = &stubUserService{users: us}
+	}
+}
+
+type stubEdgeJobService struct {
+	jobs []portainer.EdgeJob
+}
+
+func (s *stubEdgeJobService) EdgeJobs() ([]portainer.EdgeJob, error) { return s.jobs, nil }
+func (s *stubEdgeJobService) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
+	return nil, nil
+}
+func (s *stubEdgeJobService) CreateEdgeJob(edgeJob *portainer.EdgeJob) error { return nil }
+func (s *stubEdgeJobService) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
+	return nil
+}
+func (s *stubEdgeJobService) DeleteEdgeJob(ID portainer.EdgeJobID) error { return nil }
+func (s *stubEdgeJobService) GetNextIdentifier() int                     { return 0 }
+
+// WithEdgeJobs option will instruct datastore to return provided jobs
+func WithEdgeJobs(js []portainer.EdgeJob) datastoreOption {
+	return func(d *datastore) {
+		d.edgeJob = &stubEdgeJobService{jobs: js}
+	}
+}
diff --git a/api/internal/testhelpers/reverse_tunnel_service.go b/api/internal/testhelpers/reverse_tunnel_service.go
new file mode 100644
index 000000000..0dbc19d19
--- /dev/null
+++ b/api/internal/testhelpers/reverse_tunnel_service.go
@@ -0,0 +1,23 @@
+package testhelpers
+
+import portainer "github.com/portainer/portainer/api"
+
+type ReverseTunnelService struct{}
+
+func (r ReverseTunnelService) StartTunnelServer(addr, port string, snapshotService portainer.SnapshotService) error {
+	return nil
+}
+func (r ReverseTunnelService) GenerateEdgeKey(url, host string, endpointIdentifier int) string {
+	return "nil"
+}
+func (r ReverseTunnelService) SetTunnelStatusToActive(endpointID portainer.EndpointID) {}
+func (r ReverseTunnelService) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
+	return nil
+}
+func (r ReverseTunnelService) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {}
+func (r ReverseTunnelService) GetTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
+	return nil
+}
+func (r ReverseTunnelService) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *portainer.EdgeJob) {
+}
+func (r ReverseTunnelService) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {}
diff --git a/api/portainer.go b/api/portainer.go
index bd0fc0015..0d6844342 100644
--- a/api/portainer.go
+++ b/api/portainer.go
@@ -383,8 +383,8 @@ type (
 
 	// QuayRegistryData represents data required for Quay registry to work
 	QuayRegistryData struct {
-		UseOrganisation   bool   `json:"UseOrganisation"`
-		OrganisationName  string `json:"OrganisationName"`
+		UseOrganisation  bool   `json:"UseOrganisation"`
+		OrganisationName string `json:"OrganisationName"`
 	}
 
 	// JobType represents a job type
@@ -1002,8 +1002,9 @@ type (
 		Init() error
 		Close() error
 		IsNew() bool
-		MigrateData() error
+		MigrateData(force bool) error
 		CheckCurrentEdition() error
+		BackupTo(w io.Writer) error
 
 		DockerHub() DockerHubService
 		CustomTemplate() CustomTemplateService
@@ -1132,6 +1133,7 @@ type (
 		StoreCustomTemplateFileFromBytes(identifier, fileName string, data []byte) (string, error)
 		GetCustomTemplateProjectPath(identifier string) string
 		GetTemporaryPath() (string, error)
+		GetDatastorePath() string
 	}
 
 	// GitService represents a service for managing Git
@@ -1198,6 +1200,7 @@ type (
 	// ReverseTunnelService represents a service used to manage reverse tunnel connections.
 	ReverseTunnelService interface {
 		StartTunnelServer(addr, port string, snapshotService SnapshotService) error
+		StopTunnelServer() error
 		GenerateEdgeKey(url, host string, endpointIdentifier int) string
 		SetTunnelStatusToActive(endpointID EndpointID)
 		SetTunnelStatusToRequired(endpointID EndpointID) error
@@ -1240,6 +1243,7 @@ type (
 	// SnapshotService represents a service for managing endpoint snapshots
 	SnapshotService interface {
 		Start()
+		Stop()
 		SetSnapshotInterval(snapshotInterval string) error
 		SnapshotEndpoint(endpoint *Endpoint) error
 	}
diff --git a/app/constants.js b/app/constants.js
index 547c7d518..cb0e8f17f 100644
--- a/app/constants.js
+++ b/app/constants.js
@@ -22,6 +22,7 @@ angular
   .constant('API_ENDPOINT_TEAM_MEMBERSHIPS', 'api/team_memberships')
   .constant('API_ENDPOINT_TEMPLATES', 'api/templates')
   .constant('API_ENDPOINT_WEBHOOKS', 'api/webhooks')
+  .constant('API_ENDPOINT_BACKUP', 'api/backup')
   .constant('DEFAULT_TEMPLATES_URL', 'https://raw.githubusercontent.com/portainer/templates/master/templates.json')
   .constant('PAGINATION_MAX_ITEMS', 10)
   .constant('APPLICATION_CACHE_VALIDITY', 3600)
diff --git a/app/portainer/rest/backup.js b/app/portainer/rest/backup.js
new file mode 100644
index 000000000..50526c5b0
--- /dev/null
+++ b/app/portainer/rest/backup.js
@@ -0,0 +1,27 @@
+angular.module('portainer.app').factory('Backup', [
+  '$resource',
+  'API_ENDPOINT_BACKUP',
+  function BackupFactory($resource, API_ENDPOINT_BACKUP) {
+    'use strict';
+    return $resource(
+      API_ENDPOINT_BACKUP + '/:subResource/:action',
+      {},
+      {
+        download: {
+          method: 'POST',
+          responseType: 'blob',
+          ignoreLoadingBar: true,
+          transformResponse: (data, headersGetter) => ({
+            file: data,
+            name: headersGetter('Content-Disposition').replace('attachment; filename=', ''),
+          }),
+        },
+        getS3Settings: { method: 'GET', params: { subResource: 's3', action: 'settings' } },
+        saveS3Settings: { method: 'POST', params: { subResource: 's3', action: 'settings' } },
+        exportS3Backup: { method: 'POST', params: { subResource: 's3', action: 'execute' } },
+        restoreS3Backup: { method: 'POST', params: { subResource: 's3', action: 'restore' } },
+        getBackupStatus: { method: 'GET', params: { subResource: 's3', action: 'status' } },
+      }
+    );
+  },
+]);
diff --git a/app/portainer/services/api/backupService.js b/app/portainer/services/api/backupService.js
new file mode 100644
index 000000000..1ff04cda0
--- /dev/null
+++ b/app/portainer/services/api/backupService.js
@@ -0,0 +1,90 @@
+angular.module('portainer.app').factory('BackupService', [
+  '$q',
+  '$async',
+  'Backup',
+  'FileUploadService',
+  function BackupServiceFactory($q, $async, Backup, FileUploadService) {
+    'use strict';
+    const service = {};
+
+    service.downloadBackup = function (payload) {
+      return Backup.download({}, payload).$promise;
+    };
+
+    service.uploadBackup = function (file, password) {
+      return FileUploadService.uploadBackup(file, password);
+    };
+
+    service.getS3Settings = function () {
+      var deferred = $q.defer();
+
+      Backup.getS3Settings()
+        .$promise.then(function success(data) {
+          deferred.resolve(data);
+        })
+        .catch(function error(err) {
+          deferred.reject({ msg: 'Unable to retrieve backup S3 settings', err: err });
+        });
+
+      return deferred.promise;
+    };
+
+    service.saveS3Settings = function (payload) {
+      var deferred = $q.defer();
+
+      Backup.saveS3Settings({}, payload)
+        .$promise.then(function success(data) {
+          deferred.resolve(data);
+        })
+        .catch(function error(err) {
+          deferred.reject({ msg: 'Unable to save backup S3 settings', err: err });
+        });
+
+      return deferred.promise;
+    };
+
+    service.exportBackup = function (payload) {
+      var deferred = $q.defer();
+
+      Backup.exportS3Backup({}, payload)
+        .$promise.then(function success(data) {
+          deferred.resolve(data);
+        })
+        .catch(function error(err) {
+          deferred.reject({ msg: 'Unable to export backup', err: err });
+        });
+
+      return deferred.promise;
+    };
+
+    service.restoreFromS3 = function (payload) {
+      var deferred = $q.defer();
+
+      Backup.restoreS3Backup({}, payload)
+        .$promise.then(function success(data) {
+          deferred.resolve(data);
+        })
+        .catch(function error(err) {
+          deferred.reject({ msg: 'Unable to restore backup from S3', err: err });
+        });
+
+      return deferred.promise;
+    };
+
+    service.getBackupStatus = function () {
+      var deferred = $q.defer();
+
+      Backup.getBackupStatus()
+        .$promise.then(function success(data) {
+          deferred.resolve(data);
+        })
+        .catch(function error(err) {
+          deferred.reject({ msg: 'Unable to retrieve backup status', err: err });
+        });
+
+      return deferred.promise;
+    };
+
+    return service;
+  },
+]);
diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js
index a9df4606a..81340d623 100644
--- a/app/portainer/services/fileUpload.js
+++ b/app/portainer/services/fileUpload.js
@@ -61,6 +61,16 @@ angular.module('portainer.app').factory('FileUploadService', [
       });
     };
 
+    service.uploadBackup = function (file, password) {
+      return Upload.upload({
+        url: 'api/restore',
+        data: {
+          file,
+          password,
+        },
+      });
+    };
+
     service.createSwarmStack = function (stackName, swarmId, file, env, endpointId) {
       return Upload.upload({
         url: 'api/stacks?method=file&type=1&endpointId=' + endpointId,
diff --git a/app/portainer/views/init/admin/initAdmin.html b/app/portainer/views/init/admin/initAdmin.html
index b3643ea58..5e286abfc 100644
--- a/app/portainer/views/init/admin/initAdmin.html
+++ b/app/portainer/views/init/admin/initAdmin.html
@@ -11,8 +11,17 @@
       <!-- init password panel -->
       <div class="panel panel-default">
         <div class="panel-body">
+          <!-- toggle -->
+          <div style="padding-bottom: 12px;">
+            <a ng-click="togglePanel()">
+              <i ng-class="{ true: 'glyphicon glyphicon-chevron-down', false: 'glyphicon glyphicon-chevron-right' }[state.showInitPassword]" aria-hidden="true"></i
+              ><span style="padding-left: 10px;">New Portainer installation</span>
+            </a>
+          </div>
+          <!-- !toggle -->
+
           <!-- init password form -->
-          <form class="simple-box-form form-horizontal">
+          <form class="simple-box-form form-horizontal" style="padding-left: 30px;" ng-if="state.showInitPassword">
             <!-- note -->
             <div class="form-group">
               <div class="col-sm-12">
@@ -98,6 +107,123 @@
         </div>
       </div>
       <!-- !init password panel -->
+
+      <!-- restore backup panel -->
+      <div class="panel panel-default">
+        <div class="panel-body">
+          <!-- toggle -->
+          <div style="padding-bottom: 12px;">
+            <a ng-click="togglePanel()">
+              <i ng-class="{ true: 'glyphicon glyphicon-chevron-down', false: 'glyphicon glyphicon-chevron-right' }[state.showRestorePortainer]" aria-hidden="true"></i
+              ><span style="padding-left: 10px;">Restore Portainer from backup</span>
+            </a>
+          </div>
+          <!-- !toggle -->
+
+          <!-- restore form -->
+          <form class="simple-box-form form-horizontal" style="padding-left: 30px;" ng-if="state.showRestorePortainer">
+            <!-- note -->
+            <div class="form-group">
+              <div class="col-sm-9">
+                <span class="small text-muted">
+                  This will restore the Portainer metadata which contains information about the endpoints, stacks and applications, as well as the configured users.
+                </span>
+              </div>
+            </div>
+            <!-- !note -->
+            <div class="form-group">
+              <div class="boxselector_wrapper">
+                <div>
+                  <input type="radio" id="restore_file" checked="checked" />
+                  <label for="restore_file" style="padding-bottom: 20px;">
+                    <div class="boxselector_header">
+                      <i class="fa fa-upload" aria-hidden="true" style="margin-right: 2px;"></i>
+                      Upload backup file
+                    </div>
+                    <p></p>
+                  </label>
+                </div>
+                <div>
+                  <input type="radio" id="restore_s3" disabled />
+                  <label for="restore_s3" class="boxselector_disabled" style="background-color: #ffffff;">
+                    <div class="boxselector_header">
+                      <i class="fa fa-download" aria-hidden="true" style="margin-right: 2px;"></i>
+                      Retrieve from S3
+                    </div>
+                    <p>This feature is available in <a href="https://www.portainer.io/business-upsell?from=restore-s3-form" target="_blank"> Portainer Business Edition</a></p>
+                  </label>
+                </div>
+              </div>
+            </div>
+            <!-- note -->
+            <div class="form-group">
+              <div class="col-sm-12">
+                <span class="small text-muted">
+                  You can upload a backup file from your computer.
+                </span>
+              </div>
+            </div>
+            <!-- !note -->
+            <!-- select-file-input -->
+            <div class="form-group">
+              <div class="col-sm-12">
+                <button class="btn btn-sm btn-primary" ngf-select accept=".tar.gz,.encrypted" ng-model="formValues.BackupFile" auto-focus>Select file</button>
+                <span style="margin-left: 5px;">
+                  {{ formValues.BackupFile.name }}
+                  <i class="fa fa-times red-icon" ng-if="!formValues.BackupFile" aria-hidden="true"></i>
+                </span>
+              </div>
+            </div>
+            <!-- !select-file-input -->
+            <!-- password-input -->
+            <div class="form-group">
+              <label for="password" class="col-sm-3 control-label text-left">
+                Password
+                <portainer-tooltip
+                  position="bottom"
+                  message="If the backup is password protected, provide the password in order to extract the backup file, otherwise this field can be left empty."
+                ></portainer-tooltip>
+              </label>
+              <div class="col-sm-4">
+                <input type="password" class="form-control" ng-model="formValues.Password" id="password" />
+              </div>
+            </div>
+            <!-- !password-input -->
+            <!-- note -->
+            <div class="form-group">
+              <div class="col-sm-12">
+                <span class="small text-muted">
+                  You are about to restore Portainer from this backup.
+                </span>
+              </div>
+              <div class="col-sm-12">
+                <span class="small text-muted">
+                  After restoring has completed, please log in as a user that was known by the Portainer that was restored.
+                </span>
+              </div>
+            </div>
+            <!-- !note -->
+            <!-- actions -->
+            <div class="form-group">
+              <div class="col-sm-12">
+                <button
+                  type="submit"
+                  class="btn btn-primary btn-sm"
+                  ng-disabled="!formValues.BackupFile || state.backupInProgress"
+                  ng-click="uploadBackup()"
+                  button-spinner="state.backupInProgress"
+                >
+                  <span ng-hide="state.backupInProgress">Restore Portainer</span>
+                  <span ng-show="state.backupInProgress">Restoring Portainer...</span>
+                </button>
+              </div>
+            </div>
+            <!-- !actions -->
+          </form>
+          <!-- !restore backup form -->
+        </div>
+      </div>
+      <!-- !restore backup panel -->
     </div>
   </div>
   <!-- !simple box -->
diff --git a/app/portainer/views/init/admin/initAdminController.js b/app/portainer/views/init/admin/initAdminController.js
index 63bd27d02..8872bcfe1 100644
--- a/app/portainer/views/init/admin/initAdminController.js
+++ b/app/portainer/views/init/admin/initAdminController.js
@@ -8,7 +8,9 @@ angular.module('portainer.app').controller('InitAdminController', [
   'SettingsService',
   'UserService',
   'EndpointService',
-  function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService) {
+  'BackupService',
+  'StatusService',
+  function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService, BackupService, StatusService) {
     $scope.logo = StateManager.getState().application.logo;
 
     $scope.formValues = {
@@ -20,6 +22,13 @@ angular.module('portainer.app').controller('InitAdminController', [
 
     $scope.state = {
       actionInProgress: false,
+      showInitPassword: true,
+      showRestorePortainer: false,
+    };
+
+    $scope.togglePanel = function () {
+      $scope.state.showInitPassword = !$scope.state.showInitPassword;
+      $scope.state.showRestorePortainer = !$scope.state.showRestorePortainer;
     };
 
     $scope.createAdminUser = function () {
@@ -67,5 +76,36 @@ angular.module('portainer.app').controller('InitAdminController', [
         });
     }
     createAdministratorFlow();
+
+    async function waitPortainerRestart() {
+      for (let i = 0; i < 10; i++) {
+        await new Promise((resolve) => setTimeout(resolve, 5 * 1000));
+        try {
+          const status = await StatusService.status();
+          if (status && status.Version) {
+            return;
+          }
+        } catch (e) {}
+      }
+      throw 'Timeout to wait for Portainer restarting';
+    }
+
+    $scope.uploadBackup = async function () {
+      $scope.state.backupInProgress = true;
+
+      const file = $scope.formValues.BackupFile;
+      const password = $scope.formValues.Password;
+
+      try {
+        await BackupService.uploadBackup(file, password);
+        await waitPortainerRestart();
+        Notifications.success('The backup has successfully been restored');
+        $state.go('portainer.auth');
+      } catch (err) {
+        Notifications.error('Failure', err, 'Unable to restore the backup');
+      } finally {
+        $scope.state.backupInProgress = false;
+      }
+    };
   },
 ]);
diff --git a/app/portainer/views/settings/settings.html b/app/portainer/views/settings/settings.html
index bad170142..c2ca0d340 100644
--- a/app/portainer/views/settings/settings.html
+++ b/app/portainer/views/settings/settings.html
@@ -191,3 +191,87 @@
     </rd-widget>
   </div>
 </div>
+
+<div class="row">
+  <div class="col-sm-12">
+    <rd-widget>
+      <rd-widget-header icon="fa-download" title-text="Backup Portainer"></rd-widget-header>
+      <rd-widget-body>
+        <form class="form-horizontal" ng-submit="backupPortainer()" name="backupPortainerForm">
+          <div class="col-sm-12 form-section-title">
+            Backup configuration
+          </div>
+          <div class="form-group"></div>
+          <div class="form-group">
+            <div class="boxselector_wrapper">
+              <div>
+                <input type="radio" id="backup_file" checked="checked" />
+                <label for="backup_file" style="padding-bottom: 20px;">
+                  <div class="boxselector_header">
+                    <i class="fa fa-download" aria-hidden="true" style="margin-right: 2px;"></i>
+                    Download backup file
+                  </div>
+                  <p></p>
+                </label>
+              </div>
+              <div>
+                <input type="radio" id="backup_s3" disabled />
+                <label for="backup_s3" class="boxselector_disabled" style="background-color: #ffffff;">
+                  <div class="boxselector_header">
+                    <i class="fa fa-upload" aria-hidden="true" style="margin-right: 2px;"></i>
+                    Store in S3
+                  </div>
+                  <p>This feature is available in <a href="https://www.portainer.io/business-upsell?from=s3-backup-setting" target="_blank"> Portainer Business Edition</a></p>
+                </label>
+              </div>
+            </div>
+          </div>
+          <div class="col-sm-12 form-section-title">
+            Security settings
+          </div>
+          <!-- Password protect -->
+          <div class="form-group">
+            <label for="password_protect" class="col-sm-1 control-label text-left">Password protect</label>
+            <div class="col-sm-1">
+              <label class="switch"> <input type="checkbox" id="password_protect" name="password_protect" ng-model="formValues.passwordProtect" /><i></i> </label>
+            </div>
+          </div>
+          <!-- !Password protect -->
+
+          <!-- Password -->
+          <div class="form-group" ng-if="formValues.passwordProtect">
+            <label for="password" class="col-sm-1 control-label text-left">Password</label>
+            <div class="col-sm-3">
+              <input type="password" class="form-control" ng-model="formValues.password" id="password" name="password" required />
+            </div>
+          </div>
+          <div class="form-group col-md-12" ng-show="backupPortainerForm.password.$invalid">
+            <div class="small text-warning">
+              <div ng-messages="backupPortainerForm.password.$error">
+                <p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
+              </div>
+            </div>
+          </div>
+          <!-- !Password -->
+
+          <!-- actions -->
+          <div class="form-group">
+            <div class="col-sm-12">
+              <button
+                type="button"
+                class="btn btn-primary btn-sm"
+                ng-click="downloadBackup()"
+                ng-disabled="backupPortainerForm.$invalid || state.backupInProgress"
+                button-spinner="state.backupInProgress"
+              >
+                <span ng-hide="state.backupInProgress">Download backup</span>
+                <span ng-show="state.backupInProgress">Downloading backup</span>
+              </button>
+            </div>
+          </div>
+          <!-- !actions -->
+        </form>
+      </rd-widget-body>
+    </rd-widget>
+  </div>
+</div>
diff --git a/app/portainer/views/settings/settingsController.js b/app/portainer/views/settings/settingsController.js
index 86b352ced..a5438ce10 100644
--- a/app/portainer/views/settings/settingsController.js
+++ b/app/portainer/views/settings/settingsController.js
@@ -4,7 +4,10 @@ angular.module('portainer.app').controller('SettingsController', [
   'Notifications',
   'SettingsService',
   'StateManager',
-  function ($scope, $state, Notifications, SettingsService, StateManager) {
+  'BackupService',
+  'FileSaver',
+  'Blob',
+  function ($scope, $state, Notifications, SettingsService, StateManager, BackupService, FileSaver) {
     $scope.state = {
       actionInProgress: false,
       availableEdgeAgentCheckinOptions: [
@@ -21,6 +24,8 @@ angular.module('portainer.app').controller('SettingsController', [
           value: 30,
         },
       ],
+
+      backupInProgress: false,
     };
 
     $scope.formValues = {
@@ -29,6 +34,8 @@ angular.module('portainer.app').controller('SettingsController', [
       labelValue: '',
       enableEdgeComputeFeatures: false,
       enableTelemetry: false,
+      passwordProtect: false,
+      password: '',
     };
 
     $scope.removeFilteredContainerLabel = function (index) {
@@ -49,6 +56,28 @@ angular.module('portainer.app').controller('SettingsController', [
       updateSettings(settings);
     };
 
+    $scope.downloadBackup = function () {
+      const payload = {};
+      if ($scope.formValues.passwordProtect) {
+        payload.password = $scope.formValues.password;
+      }
+
+      $scope.state.backupInProgress = true;
+
+      BackupService.downloadBackup(payload)
+        .then(function success(data) {
+          const downloadData = new Blob([data.file], { type: 'application/gzip' });
+          FileSaver.saveAs(downloadData, data.name);
+          Notifications.success('Backup successfully downloaded');
+        })
+        .catch(function error(err) {
+          Notifications.error('Failure', err, 'Unable to download backup');
+        })
+        .finally(function final() {
+          $scope.state.backupInProgress = false;
+        });
+    };
+
     $scope.saveApplicationSettings = function () {
       var settings = $scope.settings;