diff --git a/apps/list.json b/apps/list.json
index 639d3ec0f..c0275066a 100644
--- a/apps/list.json
+++ b/apps/list.json
@@ -23,7 +23,7 @@
       "short_desc": "常用关系型数据库",
       "icon": "mysql.png",
       "author": "Oracle",
-      "type": "internal",
+      "type": "runtime",
       "required": [""],
       "crossVersionUpdate": false,
       "limit": 0,
@@ -37,7 +37,7 @@
       "short_desc": "高性能的HTTP和反向代理web服务器",
       "icon": "nginx.png",
       "author": "Nginx",
-      "type": "internal",
+      "type": "runtime",
       "required": [""],
       "limit": 1,
       "crossVersionUpdate": true,
@@ -51,7 +51,7 @@
       "short_desc": "老牌博客网站模版",
       "icon": "wordpress.png",
       "author": "Wordpress",
-      "type": "internal",
+      "type": "website",
       "required": ["mysql"],
       "limit": 0,
       "crossVersionUpdate": true,
diff --git a/apps/mysql/5.7.39/docker-compose.yml b/apps/mysql/5.7.39/docker-compose.yml
index 9e5b59dd3..f7c4402e2 100644
--- a/apps/mysql/5.7.39/docker-compose.yml
+++ b/apps/mysql/5.7.39/docker-compose.yml
@@ -6,7 +6,6 @@ services:
     container_name: ${CONTAINER_NAME}
     restart: always
     environment:
-      TZ: ${TZ}
       MYSQL_ROOT_PASSWORD: ${PANEL_DB_ROOT_PASSWORD}
     networks:
       - 1panel
diff --git a/apps/mysql/8.0.30/docker-compose.yml b/apps/mysql/8.0.30/docker-compose.yml
index 75d9f2cfd..8c6c3ee75 100644
--- a/apps/mysql/8.0.30/docker-compose.yml
+++ b/apps/mysql/8.0.30/docker-compose.yml
@@ -6,7 +6,6 @@ services:
     container_name: ${CONTAINER_NAME}
     restart: always
     environment:
-      TZ: ${TZ}
       MYSQL_ROOT_PASSWORD: ${PANEL_DB_ROOT_PASSWORD}
     networks:
       - 1panel
diff --git a/backend/app/api/v1/app.go b/backend/app/api/v1/app.go
index 39f4ef4dc..e94b2e983 100644
--- a/backend/app/api/v1/app.go
+++ b/backend/app/api/v1/app.go
@@ -8,14 +8,14 @@ import (
 	"strconv"
 )
 
-func (b *BaseApi) AppSearch(c *gin.Context) {
+func (b *BaseApi) SearchApp(c *gin.Context) {
 	var req dto.AppRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
 		return
 	}
 
-	list, err := appService.Page(req)
+	list, err := appService.PageApp(req)
 	if err != nil {
 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
 		return
@@ -24,7 +24,7 @@ func (b *BaseApi) AppSearch(c *gin.Context) {
 	helper.SuccessWithData(c, list)
 }
 
-func (b *BaseApi) AppSync(c *gin.Context) {
+func (b *BaseApi) SyncApp(c *gin.Context) {
 	if err := appService.SyncAppList(); err != nil {
 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
 		return
@@ -75,7 +75,7 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
 
 	helper.SuccessWithData(c, nil)
 }
-func (b *BaseApi) PageInstalled(c *gin.Context) {
+func (b *BaseApi) SearchInstalled(c *gin.Context) {
 	var req dto.AppInstalledRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
@@ -93,13 +93,13 @@ func (b *BaseApi) PageInstalled(c *gin.Context) {
 	})
 }
 
-func (b *BaseApi) InstallOperate(c *gin.Context) {
+func (b *BaseApi) OperateInstalled(c *gin.Context) {
 	var req dto.AppInstallOperate
 	if err := c.ShouldBindJSON(&req); err != nil {
 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
 		return
 	}
-	if err := appService.Operate(req); err != nil {
+	if err := appService.OperateInstall(req); err != nil {
 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
 		return
 	}
@@ -107,7 +107,7 @@ func (b *BaseApi) InstallOperate(c *gin.Context) {
 	helper.SuccessWithData(c, nil)
 }
 
-func (b *BaseApi) InstalledSync(c *gin.Context) {
+func (b *BaseApi) SyncInstalled(c *gin.Context) {
 	if err := appService.SyncAllInstalled(); err != nil {
 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
 		return
diff --git a/backend/app/model/app_container.go b/backend/app/model/app_container.go
deleted file mode 100644
index 3a0253213..000000000
--- a/backend/app/model/app_container.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package model
-
-type AppContainer struct {
-	BaseModel
-	ServiceName   string     `json:"serviceName" gorm:"type:varchar(64);not null"`
-	ContainerName string     `json:"containerName" gorm:"type:varchar(64);not null"`
-	AppInstallID  uint       `json:"appInstallId" gorm:"type:integer;not null"`
-	Port          int        `json:"port" gorm:"type:integer;not null"`
-	Auth          string     `json:"auth" gorm:"type:longtext;not null"`
-	AppInstall    AppInstall `gorm:"-"`
-}
diff --git a/backend/app/model/app_install.go b/backend/app/model/app_install.go
index 74c977de9..c9d0cc0e3 100644
--- a/backend/app/model/app_install.go
+++ b/backend/app/model/app_install.go
@@ -2,23 +2,27 @@ package model
 
 import (
 	"github.com/1Panel-dev/1Panel/global"
-	"gorm.io/gorm"
 	"path"
 )
 
 type AppInstall struct {
 	BaseModel
-	Name        string         `json:"name" gorm:"type:varchar(64);not null"`
-	Version     string         `json:"version" gorm:"type:varchar(256);not null"`
-	AppId       uint           `json:"appId" gorm:"type:integer;not null"`
-	AppDetailId uint           `json:"appDetailId" gorm:"type:integer;not null"`
-	Params      string         `json:"params"  gorm:"type:longtext;"`
-	Status      string         `json:"status" gorm:"type:varchar(256);not null"`
-	Description string         `json:"description" gorm:"type:varchar(256);"`
-	Message     string         `json:"message"  gorm:"type:longtext;"`
-	CanUpdate   bool           `json:"canUpdate"`
-	App         App            `json:"-"`
-	Containers  []AppContainer `json:"containers"`
+	Name          string `json:"name" gorm:"type:varchar(64);not null"`
+	AppId         uint   `json:"appId" gorm:"type:integer;not null"`
+	AppDetailId   uint   `json:"appDetailId" gorm:"type:integer;not null"`
+	Version       string `json:"version" gorm:"type:varchar(64);not null"`
+	Param         string `json:"param"  gorm:"type:longtext;"`
+	Env           string `json:"env"  gorm:"type:longtext;"`
+	DockerCompose string `json:"dockerCompose"  gorm:"type:longtext;"`
+	Status        string `json:"status" gorm:"type:varchar(256);not null"`
+	Description   string `json:"description" gorm:"type:varchar(256);"`
+	Message       string `json:"message"  gorm:"type:longtext;"`
+	CanUpdate     bool   `json:"canUpdate"`
+	ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"`
+	ServiceName   string `json:"ServiceName" gorm:"type:varchar(256);not null"`
+	HttpPort      int    `json:"httpPort" gorm:"type:integer;not null"`
+	HttpsPort     int    `json:"httpsPort" gorm:"type:integer;not null"`
+	App           App    `json:"-"`
 }
 
 func (i *AppInstall) GetPath() string {
@@ -28,12 +32,3 @@ func (i *AppInstall) GetPath() string {
 func (i *AppInstall) GetComposePath() string {
 	return path.Join(global.CONF.System.AppDir, i.App.Key, i.Name, "docker-compose.yml")
 }
-
-func (i *AppInstall) BeforeDelete(tx *gorm.DB) (err error) {
-
-	if err = tx.Model(AppContainer{}).Debug().Where("app_install_id = ?", i.ID).Delete(AppContainer{}).Error; err != nil {
-		return err
-	}
-
-	return
-}
diff --git a/backend/app/model/app_install_resource.go b/backend/app/model/app_install_resource.go
new file mode 100644
index 000000000..5cdbabe3e
--- /dev/null
+++ b/backend/app/model/app_install_resource.go
@@ -0,0 +1,9 @@
+package model
+
+type AppInstallResource struct {
+	BaseModel
+	AppInstallId uint   `json:"appInstallId" gorm:"type:integer;not null;"`
+	LinkId       uint   `json:"linkId"  gorm:"type:integer;not null;"`
+	ResourceId   uint   `json:"resourceId" gorm:"type:integer;"`
+	Key          string `json:"key" gorm:"type:varchar(64);not null"`
+}
diff --git a/backend/app/model/database.go b/backend/app/model/database.go
index 0f7fa5360..7b96c804c 100644
--- a/backend/app/model/database.go
+++ b/backend/app/model/database.go
@@ -2,10 +2,9 @@ package model
 
 type Database struct {
 	BaseModel
-	AppContainerId uint   `json:"appContainerId" gorm:"type:integer;not null"`
-	AppInstallId   uint   `json:"appInstallId" gorm:"type:integer;not null"`
-	Key            string `json:"key" gorm:"type:varchar(64);not null"`
-	Dbname         string `json:"dbname" gorm:"type:varchar(256);not null"`
-	Username       string `json:"username" gorm:"type:varchar(256);not null"`
-	Password       string `json:"password" gorm:"type:varchar(256);not null"`
+	AppInstallId uint   `json:"appInstallId" gorm:"type:integer;not null"`
+	Key          string `json:"key" gorm:"type:varchar(64);not null"`
+	Dbname       string `json:"dbname" gorm:"type:varchar(256);not null"`
+	Username     string `json:"username" gorm:"type:varchar(256);not null"`
+	Password     string `json:"password" gorm:"type:varchar(256);not null"`
 }
diff --git a/backend/app/repo/app_container.go b/backend/app/repo/app_container.go
deleted file mode 100644
index b7e7f7820..000000000
--- a/backend/app/repo/app_container.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package repo
-
-import (
-	"context"
-	"github.com/1Panel-dev/1Panel/app/model"
-	"github.com/1Panel-dev/1Panel/global"
-	"gorm.io/gorm"
-)
-
-type AppContainerRepo struct {
-}
-
-func (a AppContainerRepo) WithAppId(appId uint) DBOption {
-	return func(db *gorm.DB) *gorm.DB {
-		return db.Where("app_id = ?", appId)
-	}
-}
-
-func (a AppContainerRepo) WithServiceName(serviceName string) DBOption {
-	return func(db *gorm.DB) *gorm.DB {
-		return db.Where("service_name = ?", serviceName)
-	}
-}
-
-func (a AppContainerRepo) GetBy(opts ...DBOption) ([]model.AppContainer, error) {
-	db := global.DB.Model(&model.AppContainer{})
-	var appContainers []model.AppContainer
-	for _, opt := range opts {
-		db = opt(db)
-	}
-	err := db.Find(&appContainers).Error
-	return appContainers, err
-}
-
-func (a AppContainerRepo) GetFirst(opts ...DBOption) (model.AppContainer, error) {
-	db := global.DB.Model(&model.AppContainer{})
-	var appContainer model.AppContainer
-	for _, opt := range opts {
-		db = opt(db)
-	}
-	err := db.Find(&appContainer).Error
-	return appContainer, err
-}
-
-func (a AppContainerRepo) Create(container *model.AppContainer) error {
-	db := global.DB.Model(&model.AppContainer{})
-	return db.Create(&container).Error
-}
-
-func (a AppContainerRepo) BatchCreate(ctx context.Context, containers []*model.AppContainer) error {
-	db := ctx.Value("db").(*gorm.DB)
-	return db.Model(&model.AppContainer{}).Create(&containers).Error
-}
diff --git a/backend/app/repo/app_install.go b/backend/app/repo/app_install.go
index 55dc4e7cb..23eaf00fa 100644
--- a/backend/app/repo/app_install.go
+++ b/backend/app/repo/app_install.go
@@ -25,13 +25,19 @@ func (a AppInstallRepo) WithStatus(status string) DBOption {
 	}
 }
 
+func (a AppInstallRepo) WithServiceName(serviceName string) DBOption {
+	return func(db *gorm.DB) *gorm.DB {
+		return db.Where("service_name = ?", serviceName)
+	}
+}
+
 func (a AppInstallRepo) GetBy(opts ...DBOption) ([]model.AppInstall, error) {
 	db := global.DB.Model(&model.AppInstall{})
 	for _, opt := range opts {
 		db = opt(db)
 	}
 	var install []model.AppInstall
-	err := db.Preload("App").Preload("Containers").Find(&install).Error
+	err := db.Preload("App").Find(&install).Error
 	return install, err
 }
 
@@ -41,26 +47,21 @@ func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
 		db = opt(db)
 	}
 	var install model.AppInstall
-	err := db.Preload("App").Preload("Containers").First(&install).Error
+	err := db.Preload("App").First(&install).Error
 	return install, err
 }
 
 func (a AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error {
-	db := ctx.Value("db").(*gorm.DB).Model(&model.AppInstall{})
+	db := getTx(ctx).Model(&model.AppInstall{})
 	return db.Create(&install).Error
 }
 
 func (a AppInstallRepo) Save(install model.AppInstall) error {
-	db := global.DB
-	return db.Save(&install).Error
+	return getDb().Save(&install).Error
 }
 
 func (a AppInstallRepo) DeleteBy(opts ...DBOption) error {
-	db := global.DB.Model(&model.AppInstall{})
-	for _, opt := range opts {
-		db = opt(db)
-	}
-	return db.Delete(&model.AppInstall{}).Error
+	return getDb(opts...).Delete(&model.AppInstall{}).Error
 }
 
 func (a AppInstallRepo) Delete(ctx context.Context, install model.AppInstall) error {
@@ -76,7 +77,7 @@ func (a AppInstallRepo) Page(page, size int, opts ...DBOption) (int64, []model.A
 	}
 	count := int64(0)
 	db = db.Count(&count)
-	err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("App").Preload("Containers").Find(&apps).Error
+	err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("App").Find(&apps).Error
 	return count, apps, err
 }
 
diff --git a/backend/app/repo/app_install_resource.go b/backend/app/repo/app_install_resource.go
new file mode 100644
index 000000000..bb033abbb
--- /dev/null
+++ b/backend/app/repo/app_install_resource.go
@@ -0,0 +1,36 @@
+package repo
+
+import (
+	"context"
+	"github.com/1Panel-dev/1Panel/app/model"
+	"github.com/1Panel-dev/1Panel/global"
+	"gorm.io/gorm"
+)
+
+type AppInstallResourceRpo struct {
+}
+
+func (a AppInstallResourceRpo) WithAppInstallId(appInstallId uint) DBOption {
+	return func(db *gorm.DB) *gorm.DB {
+		return db.Where("app_install_id = ?", appInstallId)
+	}
+}
+
+func (a AppInstallResourceRpo) GetBy(opts ...DBOption) ([]model.AppInstallResource, error) {
+	db := global.DB.Model(&model.AppInstallResource{})
+	var resources []model.AppInstallResource
+	for _, opt := range opts {
+		db = opt(db)
+	}
+	err := db.Find(&resources).Error
+	return resources, err
+}
+
+func (a AppInstallResourceRpo) Create(ctx context.Context, resource *model.AppInstallResource) error {
+	db := getTx(ctx).Model(&model.AppInstallResource{})
+	return db.Create(&resource).Error
+}
+
+func (a AppInstallResourceRpo) DeleteBy(ctx context.Context, opts ...DBOption) error {
+	return getTx(ctx, opts...).Delete(&model.AppInstallResource{}).Error
+}
diff --git a/backend/app/repo/common.go b/backend/app/repo/common.go
index a5962db67..a628f5ca5 100644
--- a/backend/app/repo/common.go
+++ b/backend/app/repo/common.go
@@ -1,6 +1,10 @@
 package repo
 
-import "gorm.io/gorm"
+import (
+	"context"
+	"github.com/1Panel-dev/1Panel/global"
+	"gorm.io/gorm"
+)
 
 type DBOption func(*gorm.DB) *gorm.DB
 
@@ -58,3 +62,19 @@ func (c *CommonRepo) WithIdsIn(ids []uint) DBOption {
 		return g.Where("id in (?)", ids)
 	}
 }
+
+func getTx(ctx context.Context, opts ...DBOption) *gorm.DB {
+	tx := ctx.Value("db").(*gorm.DB)
+	for _, opt := range opts {
+		tx = opt(tx)
+	}
+	return tx
+}
+
+func getDb(opts ...DBOption) *gorm.DB {
+	db := global.DB
+	for _, opt := range opts {
+		db = opt(db)
+	}
+	return db
+}
diff --git a/backend/app/repo/entry.go b/backend/app/repo/entry.go
index a666e1411..025b9b643 100644
--- a/backend/app/repo/entry.go
+++ b/backend/app/repo/entry.go
@@ -14,7 +14,7 @@ type RepoGroup struct {
 	TagRepo
 	AppDetailRepo
 	AppInstallRepo
-	AppContainerRepo
+	AppInstallResourceRpo
 	DatabaseRepo
 }
 
diff --git a/backend/app/service/app.go b/backend/app/service/app.go
index d714950c1..c96d49e42 100644
--- a/backend/app/service/app.go
+++ b/backend/app/service/app.go
@@ -4,6 +4,7 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"errors"
+	"fmt"
 	"github.com/1Panel-dev/1Panel/app/dto"
 	"github.com/1Panel-dev/1Panel/app/model"
 	"github.com/1Panel-dev/1Panel/app/repo"
@@ -15,18 +16,15 @@ import (
 	"github.com/1Panel-dev/1Panel/utils/files"
 	"golang.org/x/net/context"
 	"gopkg.in/yaml.v3"
-	"math"
 	"os"
 	"path"
-	"reflect"
-	"strconv"
 	"strings"
 )
 
 type AppService struct {
 }
 
-func (a AppService) Page(req dto.AppRequest) (interface{}, error) {
+func (a AppService) PageApp(req dto.AppRequest) (interface{}, error) {
 
 	var opts []repo.DBOption
 	opts = append(opts, commonRepo.WithOrderBy("name"))
@@ -147,7 +145,7 @@ func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO,
 	return appDetailDTO, nil
 }
 
-func (a AppService) Operate(req dto.AppInstallOperate) error {
+func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
 	if err != nil {
 		return err
@@ -178,46 +176,24 @@ func (a AppService) Operate(req dto.AppInstallOperate) error {
 		op := files.NewFileOp()
 		appDir := install.GetPath()
 		dir, _ := os.Stat(appDir)
+		if dir != nil {
+			out, err := compose.Down(dockerComposePath)
+			if err != nil {
+				return handleErr(install, err, out)
+			}
+			if err := op.DeleteDir(appDir); err != nil {
+				return err
+			}
+		}
 
 		tx := global.DB.Begin()
 		ctx := context.WithValue(context.Background(), "db", tx)
-		if dir == nil {
-			if err := appInstallRepo.Delete(ctx, install); err != nil {
-				return err
-			}
-			tx.Commit()
-			return nil
-		}
-		out, err := compose.Down(dockerComposePath)
-		if err != nil {
-			tx.Rollback()
-			return handleErr(install, err, out)
-		}
-		if err := op.DeleteDir(appDir); err != nil {
-			tx.Rollback()
-			return err
-		}
+
 		if err := appInstallRepo.Delete(ctx, install); err != nil {
 			tx.Rollback()
 			return err
 		}
-
-		database, _ := dataBaseRepo.GetFirst(dataBaseRepo.ByAppInstallId(install.ID))
-		if reflect.DeepEqual(database, model.Database{}) {
-			tx.Commit()
-			return nil
-		}
-		if err := dataBaseRepo.DeleteBy(ctx, dataBaseRepo.ByAppInstallId(install.ID)); err != nil {
-			tx.Rollback()
-			return err
-		}
-		container, err := appContainerRepo.GetFirst(commonRepo.WithByID(database.AppContainerId))
-		if err != nil {
-			tx.Commit()
-			return nil
-		}
-
-		if err := execDockerCommand(database, container, Delete); err != nil {
+		if err := deleteLink(ctx, &install); err != nil {
 			tx.Rollback()
 			return err
 		}
@@ -235,25 +211,15 @@ func (a AppService) Operate(req dto.AppInstallOperate) error {
 	return appInstallRepo.Save(install)
 }
 
-func handleErr(install model.AppInstall, err error, out string) error {
-	reErr := err
-	install.Message = err.Error()
-	if out != "" {
-		install.Message = out
-		reErr = errors.New(out)
-	}
-	_ = appInstallRepo.Save(install)
-	return reErr
-}
-
 func (a AppService) Install(name string, appDetailId uint, params map[string]interface{}) error {
 
-	port, ok := params["PANEL_APP_PORT_HTTP"]
-	if ok {
-		portStr := strconv.FormatFloat(port.(float64), 'f', -1, 32)
-		if common.ScanPort(portStr) {
-			return errors.New("port is in used")
-		}
+	httpPort, err := checkPort("PANEL_APP_PORT_HTTP", params)
+	if err != nil {
+		return errors.New(fmt.Sprintf("%d port is in used", httpPort))
+	}
+	httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", params)
+	if err != nil {
+		return errors.New(fmt.Sprintf("%d port is in used", httpsPort))
 	}
 
 	appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(appDetailId))
@@ -268,8 +234,7 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int
 	if err := checkRequiredAndLimit(app); err != nil {
 		return err
 	}
-	composeFilePath, err := copyAppData(app.Key, appDetail.Version, name, params)
-	if err != nil {
+	if err := copyAppData(app.Key, appDetail.Version, name, params); err != nil {
 		return err
 	}
 
@@ -283,42 +248,10 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int
 		AppDetailId: appDetail.ID,
 		Version:     appDetail.Version,
 		Status:      constant.Installing,
-		Params:      string(paramByte),
-	}
-
-	var (
-		auth     string
-		dbConfig dto.AppDatabase
-	)
-	tags, err := tagRepo.GetByAppId(app.ID)
-	if err != nil {
-		return err
-	}
-	for _, tag := range tags {
-		if tag.Key == "Database" {
-			var authParam dto.AuthParam
-			paramByte, err := json.Marshal(params)
-			if err != nil {
-				return err
-			}
-			if err := json.Unmarshal(paramByte, &authParam); err != nil {
-				return err
-			}
-			authByte, err := json.Marshal(authParam)
-			if err != nil {
-				return err
-			}
-			auth = string(authByte)
-		}
-		if tag.Key == "WebSite" {
-			paramByte, err := json.Marshal(params)
-			if err != nil {
-				return err
-			}
-			if err := json.Unmarshal(paramByte, &dbConfig); err != nil {
-				return err
-			}
-		}
+		Env:         string(paramByte),
+		HttpPort:    httpPort,
+		HttpsPort:   httpsPort,
+		App:         app,
 	}
 
 	composeMap := make(map[string]interface{})
@@ -327,27 +260,14 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int
 	}
 	servicesMap := composeMap["services"].(map[string]interface{})
 	changeKeys := make(map[string]string, len(servicesMap))
-	var appContainers []*model.AppContainer
 	for k, v := range servicesMap {
 		serviceName := k + "-" + common.RandStr(4)
 		changeKeys[k] = serviceName
 		value := v.(map[string]interface{})
 		containerName := constant.ContainerPrefix + k + "-" + common.RandStr(4)
 		value["container_name"] = containerName
-		servicePort := 0
-
-		port, ok := params["PANEL_APP_PORT_HTTP"]
-		if ok {
-			portN := int(math.Ceil(port.(float64)))
-			servicePort = portN
-		}
-
-		appContainers = append(appContainers, &model.AppContainer{
-			ServiceName:   serviceName,
-			ContainerName: containerName,
-			Port:          servicePort,
-			Auth:          auth,
-		})
+		appInstall.ServiceName = serviceName
+		appInstall.ContainerName = containerName
 	}
 	for k, v := range changeKeys {
 		servicesMap[v] = servicesMap[k]
@@ -357,61 +277,25 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int
 	if err != nil {
 		return err
 	}
+	appInstall.DockerCompose = string(composeByte)
+
 	fileOp := files.NewFileOp()
-	if err := fileOp.WriteFile(composeFilePath, strings.NewReader(string(composeByte)), 0775); err != nil {
+	if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil {
 		return err
 	}
 
 	tx := global.DB.Begin()
 	ctx := context.WithValue(context.Background(), "db", tx)
-
 	if err := appInstallRepo.Create(ctx, &appInstall); err != nil {
 		tx.Rollback()
 		return err
 	}
-	for _, c := range appContainers {
-		c.AppInstallID = appInstall.ID
-	}
-	if err := appContainerRepo.BatchCreate(ctx, appContainers); err != nil {
+	if err := createLink(ctx, app, &appInstall, params); err != nil {
 		tx.Rollback()
 		return err
 	}
-	if !reflect.DeepEqual(dbConfig, dto.AppDatabase{}) {
-		container, err := appContainerRepo.GetFirst(appContainerRepo.WithServiceName(dbConfig.ServiceName))
-		if err != nil {
-			tx.Rollback()
-			return err
-		}
-		install, err := appInstallRepo.GetFirst(commonRepo.WithByID(container.AppInstallID))
-		if err != nil {
-			tx.Rollback()
-			return err
-		}
-		app, err := appRepo.GetFirst(commonRepo.WithByID(install.ID))
-		if err != nil {
-			tx.Rollback()
-			return err
-		}
-		var database model.Database
-		database.AppContainerId = container.ID
-		database.Dbname = dbConfig.DbName
-		database.Username = dbConfig.DbUser
-		database.Password = dbConfig.Password
-		database.AppInstallId = appInstall.ID
-		database.Key = app.Key
-		if err := dataBaseRepo.Create(ctx, &database); err != nil {
-			tx.Rollback()
-			return err
-		}
-
-		if err := execDockerCommand(database, container, Add); err != nil {
-			tx.Rollback()
-			return err
-		}
-	}
-
 	tx.Commit()
-	go upApp(composeFilePath, appInstall)
+	go upApp(appInstall.GetComposePath(), appInstall)
 	return nil
 }
 
@@ -441,12 +325,10 @@ func (a AppService) GetServices(key string) ([]dto.AppService, error) {
 	}
 	var res []dto.AppService
 	for _, install := range installs {
-		for _, container := range install.Containers {
-			res = append(res, dto.AppService{
-				Label: install.Name,
-				Value: container.ServiceName,
-			})
-		}
+		res = append(res, dto.AppService{
+			Label: install.Name,
+			Value: install.ServiceName,
+		})
 	}
 	return res, nil
 }
@@ -456,10 +338,12 @@ func (a AppService) SyncInstalled(installId uint) error {
 	if err != nil {
 		return err
 	}
-	var containerNames []string
-	for _, a := range appInstall.Containers {
-		containerNames = append(containerNames, a.ContainerName)
+
+	containerNames, err := getContainerNames(appInstall)
+	if err != nil {
+		return err
 	}
+
 	cli, err := docker.NewClient()
 	if err != nil {
 		return err
diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go
index 260c50283..cc8017b11 100644
--- a/backend/app/service/app_utils.go
+++ b/backend/app/service/app_utils.go
@@ -1,6 +1,7 @@
 package service
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"github.com/1Panel-dev/1Panel/app/dto"
@@ -8,11 +9,14 @@ import (
 	"github.com/1Panel-dev/1Panel/constant"
 	"github.com/1Panel-dev/1Panel/global"
 	"github.com/1Panel-dev/1Panel/utils/cmd"
+	"github.com/1Panel-dev/1Panel/utils/common"
 	"github.com/1Panel-dev/1Panel/utils/compose"
 	"github.com/1Panel-dev/1Panel/utils/files"
 	"github.com/joho/godotenv"
 	"github.com/pkg/errors"
+	"math"
 	"path"
+	"reflect"
 	"strconv"
 )
 
@@ -23,15 +27,15 @@ var (
 	Delete DatabaseOp = "delete"
 )
 
-func execDockerCommand(database model.Database, container model.AppContainer, op DatabaseOp) error {
+func execDockerCommand(database model.Database, dbInstall model.AppInstall, op DatabaseOp) error {
 	var auth dto.AuthParam
 	var dbConfig dto.AppDatabase
 	dbConfig.Password = database.Password
 	dbConfig.DbUser = database.Username
 	dbConfig.DbName = database.Dbname
-	json.Unmarshal([]byte(container.Auth), &auth)
+	json.Unmarshal([]byte(dbInstall.Param), &auth)
 	execConfig := dto.ContainerExec{
-		ContainerName: container.ContainerName,
+		ContainerName: dbInstall.ContainerName,
 		Auth:          auth,
 		DbParam:       dbConfig,
 	}
@@ -59,6 +63,118 @@ func getSqlStr(key string, operate DatabaseOp, exec dto.ContainerExec) string {
 	return str
 }
 
+func checkPort(key string, params map[string]interface{}) (int, error) {
+
+	port, ok := params[key]
+	if ok {
+		portN := int(math.Ceil(port.(float64)))
+		if common.ScanPort(portN) {
+			return portN, errors.New("port is in used")
+		} else {
+			return portN, nil
+		}
+	}
+	return 0, nil
+}
+
+func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error {
+	var dbConfig dto.AppDatabase
+	if app.Type == "runtime" {
+		var authParam dto.AuthParam
+		paramByte, err := json.Marshal(params)
+		if err != nil {
+			return err
+		}
+		if err := json.Unmarshal(paramByte, &authParam); err != nil {
+			return err
+		}
+		authByte, err := json.Marshal(authParam)
+		if err != nil {
+			return err
+		}
+		appInstall.Param = string(authByte)
+	}
+	if app.Type == "website" {
+		paramByte, err := json.Marshal(params)
+		if err != nil {
+			return err
+		}
+		if err := json.Unmarshal(paramByte, &dbConfig); err != nil {
+			return err
+		}
+	}
+
+	if !reflect.DeepEqual(dbConfig, dto.AppDatabase{}) {
+		dbInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(dbConfig.ServiceName))
+		if err != nil {
+			return err
+		}
+		var database model.Database
+		database.Dbname = dbConfig.DbName
+		database.Username = dbConfig.DbUser
+		database.Password = dbConfig.Password
+		database.AppInstallId = dbInstall.ID
+		database.Key = dbInstall.App.Key
+		if err := dataBaseRepo.Create(ctx, &database); err != nil {
+			return err
+		}
+		var installResource model.AppInstallResource
+		installResource.ResourceId = database.ID
+		installResource.AppInstallId = appInstall.ID
+		installResource.LinkId = dbInstall.ID
+		installResource.Key = dbInstall.App.Key
+		if err := appInstallResourceRepo.Create(ctx, &installResource); err != nil {
+			return err
+		}
+		if err := execDockerCommand(database, dbInstall, Add); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func deleteLink(ctx context.Context, install *model.AppInstall) error {
+	resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
+	if len(resources) == 0 {
+		return nil
+	}
+	for _, re := range resources {
+		if re.Key == "mysql" {
+			database, _ := dataBaseRepo.GetFirst(commonRepo.WithByID(re.ResourceId))
+			if reflect.DeepEqual(database, model.Database{}) {
+				continue
+			}
+			appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(database.AppInstallId))
+			if err != nil {
+				return nil
+			}
+			if err := execDockerCommand(database, appInstall, Delete); err != nil {
+				return err
+			}
+			if err := dataBaseRepo.DeleteBy(ctx, commonRepo.WithByID(database.ID)); err != nil {
+				return err
+			}
+		}
+	}
+	return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
+}
+
+func getContainerNames(install model.AppInstall) ([]string, error) {
+	composeMap := install.DockerCompose
+	envMap := make(map[string]string)
+	_ = json.Unmarshal([]byte(install.Env), &envMap)
+	project, err := compose.GetComposeProject([]byte(composeMap), envMap)
+	if err != nil {
+		return nil, err
+	}
+	var containerNames []string
+	for _, service := range project.AllServices() {
+		containerNames = append(containerNames, service.ContainerName)
+	}
+	return containerNames, nil
+}
+
 func checkRequiredAndLimit(app model.App) error {
 
 	if app.Limit > 0 {
@@ -103,7 +219,7 @@ func checkRequiredAndLimit(app model.App) error {
 	return nil
 }
 
-func copyAppData(key, version, installName string, params map[string]interface{}) (composeFilePath string, err error) {
+func copyAppData(key, version, installName string, params map[string]interface{}) (err error) {
 	resourceDir := path.Join(global.CONF.System.ResourceDir, "apps", key, version)
 	installDir := path.Join(global.CONF.System.AppDir, key)
 	installVersionDir := path.Join(installDir, version)
@@ -115,7 +231,6 @@ func copyAppData(key, version, installName string, params map[string]interface{}
 	if err = fileOp.Rename(installVersionDir, appDir); err != nil {
 		return
 	}
-	composeFilePath = path.Join(appDir, "docker-compose.yml")
 	envPath := path.Join(appDir, ".env")
 
 	envParams := make(map[string]string, len(params))
@@ -197,3 +312,14 @@ func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App {
 	}
 	return apps
 }
+
+func handleErr(install model.AppInstall, err error, out string) error {
+	reErr := err
+	install.Message = err.Error()
+	if out != "" {
+		install.Message = out
+		reErr = errors.New(out)
+	}
+	_ = appInstallRepo.Save(install)
+	return reErr
+}
diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go
index e9c6d2403..d3ad3316d 100644
--- a/backend/app/service/entry.go
+++ b/backend/app/service/entry.go
@@ -18,19 +18,19 @@ type ServiceGroup struct {
 var ServiceGroupApp = new(ServiceGroup)
 
 var (
-	hostRepo         = repo.RepoGroupApp.HostRepo
-	backupRepo       = repo.RepoGroupApp.BackupRepo
-	groupRepo        = repo.RepoGroupApp.GroupRepo
-	commandRepo      = repo.RepoGroupApp.CommandRepo
-	operationRepo    = repo.RepoGroupApp.OperationRepo
-	commonRepo       = repo.RepoGroupApp.CommonRepo
-	cronjobRepo      = repo.RepoGroupApp.CronjobRepo
-	settingRepo      = repo.RepoGroupApp.SettingRepo
-	appRepo          = repo.RepoGroupApp.AppRepo
-	appTagRepo       = repo.RepoGroupApp.AppTagRepo
-	appDetailRepo    = repo.RepoGroupApp.AppDetailRepo
-	tagRepo          = repo.RepoGroupApp.TagRepo
-	appInstallRepo   = repo.RepoGroupApp.AppInstallRepo
-	appContainerRepo = repo.RepoGroupApp.AppContainerRepo
-	dataBaseRepo     = repo.RepoGroupApp.DatabaseRepo
+	hostRepo               = repo.RepoGroupApp.HostRepo
+	backupRepo             = repo.RepoGroupApp.BackupRepo
+	groupRepo              = repo.RepoGroupApp.GroupRepo
+	commandRepo            = repo.RepoGroupApp.CommandRepo
+	operationRepo          = repo.RepoGroupApp.OperationRepo
+	commonRepo             = repo.RepoGroupApp.CommonRepo
+	cronjobRepo            = repo.RepoGroupApp.CronjobRepo
+	settingRepo            = repo.RepoGroupApp.SettingRepo
+	appRepo                = repo.RepoGroupApp.AppRepo
+	appTagRepo             = repo.RepoGroupApp.AppTagRepo
+	appDetailRepo          = repo.RepoGroupApp.AppDetailRepo
+	tagRepo                = repo.RepoGroupApp.TagRepo
+	appInstallRepo         = repo.RepoGroupApp.AppInstallRepo
+	appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
+	dataBaseRepo           = repo.RepoGroupApp.DatabaseRepo
 )
diff --git a/backend/go.mod b/backend/go.mod
index 418d0d9e2..abc110275 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -5,7 +5,9 @@ go 1.18
 require (
 	github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible
 	github.com/aws/aws-sdk-go v1.44.99
+	github.com/compose-spec/compose-go v1.6.0
 	github.com/dgraph-io/badger/v3 v3.2103.2
+	github.com/docker/docker v20.10.18+incompatible
 	github.com/fsnotify/fsnotify v1.5.4
 	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
 	github.com/gabriel-vasile/mimetype v1.4.1
@@ -43,6 +45,7 @@ require (
 	golang.org/x/net v0.0.0-20220722155237-a158d28d115b
 	golang.org/x/text v0.3.7
 	gopkg.in/yaml.v2 v2.4.0
+	gopkg.in/yaml.v3 v3.0.1
 	gorm.io/driver/mysql v1.3.5
 	gorm.io/driver/sqlite v1.3.6
 	gorm.io/gorm v1.23.8
@@ -55,10 +58,10 @@ require (
 	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
 	github.com/andybalholm/brotli v1.0.4 // indirect
 	github.com/cespare/xxhash v1.1.0 // indirect
-	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/dgraph-io/ristretto v0.1.0 // indirect
+	github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
 	github.com/docker/distribution v2.8.1+incompatible // indirect
-	github.com/docker/docker v20.10.18+incompatible // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/dsnet/compress v0.0.1 // indirect
@@ -84,6 +87,7 @@ require (
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/gorilla/securecookie v1.1.1 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/imdario/mergo v0.3.13 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -98,12 +102,15 @@ require (
 	github.com/mailru/easyjson v0.7.6 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/mattn/go-shellwords v1.0.12 // indirect
 	github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
 	github.com/minio/md5-simd v1.1.2 // indirect
 	github.com/minio/sha256-simd v1.0.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
@@ -120,17 +127,20 @@ require (
 	github.com/tklauser/numcpus v0.4.0 // indirect
 	github.com/ugorji/go/codec v1.2.7 // indirect
 	github.com/ulikunitz/xz v0.5.10 // indirect
+	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
+	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/yusufpapurcu/wmi v1.2.2 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	go.opentelemetry.io/otel v1.0.0 // indirect
 	go.opentelemetry.io/otel/trace v1.0.0 // indirect
 	golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
 	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	golang.org/x/tools v0.1.12 // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/ini.v1 v1.66.6 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/backend/go.sum b/backend/go.sum
index 8072eb0aa..4b6ec5882 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -36,6 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -60,8 +62,9 @@ github.com/aws/aws-sdk-go v1.44.99/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -71,12 +74,15 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/compose-spec/compose-go v1.6.0 h1:7Ol/UULMUtbPmB0EYrETASRoum821JpOh/XaEf+hN+Q=
+github.com/compose-spec/compose-go v1.6.0/go.mod h1:os+Ulh2jlZxY1XT1hbciERadjSUU/BtZ6+gcN7vD7J0=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -87,6 +93,8 @@ github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/Lu
 github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb h1:oCCuuU3kMO3sjZH/p7LamvQNW9SWoT4yQuMGcdSxGAE=
+github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4=
 github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
 github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc=
@@ -263,6 +271,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
 github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
@@ -336,6 +346,8 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
+github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -351,6 +363,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
+github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -359,6 +373,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
 github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
 github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
 github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
@@ -386,6 +402,7 @@ github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuw
 github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
 github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+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/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs=
@@ -471,6 +488,12 @@ github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A
 github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
 github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f h1:C8De+7emQKojPBC+mXA0fr39XN5mKjRm9IUzdxI4whI=
 github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -541,7 +564,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -605,6 +627,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -646,6 +670,7 @@ golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -686,6 +711,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -725,15 +751,12 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -846,6 +869,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/driver/mysql v1.3.5 h1:iWBTVW/8Ij5AG4e0G/zqzaJblYkBI1VIL1LG2HUGsvY=
@@ -857,6 +881,8 @@ gorm.io/driver/sqlserver v1.3.2 h1:yYt8f/xdAKLY7lCCyXxIUEgZ/WsURos3dHrx8MKFGAk=
 gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=
 gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
+gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
+gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go
index 96032c324..c3fc6c03f 100644
--- a/backend/init/migration/migrations/init.go
+++ b/backend/init/migration/migrations/init.go
@@ -150,6 +150,6 @@ var AddTableCronjob = &gormigrate.Migration{
 var AddTableApp = &gormigrate.Migration{
 	ID: "20200921-add-table-app",
 	Migrate: func(tx *gorm.DB) error {
-		return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppContainer{}, &model.Database{})
+		return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{}, &model.Database{})
 	},
 }
diff --git a/backend/router/ro_app.go b/backend/router/ro_app.go
index 85405bc90..9dd23ac35 100644
--- a/backend/router/ro_app.go
+++ b/backend/router/ro_app.go
@@ -15,14 +15,14 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
 
 	baseApi := v1.ApiGroupApp.BaseApi
 	{
-		appRouter.POST("/sync", baseApi.AppSync)
-		appRouter.POST("/search", baseApi.AppSearch)
+		appRouter.POST("/sync", baseApi.SyncApp)
+		appRouter.POST("/search", baseApi.SearchApp)
 		appRouter.GET("/:id", baseApi.GetApp)
 		appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail)
 		appRouter.POST("/install", baseApi.InstallApp)
-		appRouter.POST("/installed", baseApi.PageInstalled)
-		appRouter.POST("/installed/op", baseApi.InstallOperate)
-		appRouter.POST("/installed/sync", baseApi.InstalledSync)
+		appRouter.POST("/installed", baseApi.SearchInstalled)
+		appRouter.POST("/installed/op", baseApi.OperateInstalled)
+		appRouter.POST("/installed/sync", baseApi.SyncInstalled)
 		appRouter.GET("/services/:key", baseApi.GetServices)
 	}
 }
diff --git a/backend/utils/common/common.go b/backend/utils/common/common.go
index eafc9fa89..4d0685ee6 100644
--- a/backend/utils/common/common.go
+++ b/backend/utils/common/common.go
@@ -78,9 +78,9 @@ func RandStr(n int) string {
 	return string(b)
 }
 
-func ScanPort(port string) bool {
+func ScanPort(port int) bool {
 
-	ln, err := net.Listen("tcp", ":"+port)
+	ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
 	if err != nil {
 		return true
 	}
diff --git a/backend/utils/compose/compose.go b/backend/utils/compose/compose.go
index c98562a4b..fbdd234dd 100644
--- a/backend/utils/compose/compose.go
+++ b/backend/utils/compose/compose.go
@@ -1,6 +1,10 @@
 package compose
 
-import "os/exec"
+import (
+	"github.com/compose-spec/compose-go/loader"
+	"github.com/compose-spec/compose-go/types"
+	"os/exec"
+)
 
 func Up(filePath string) (string, error) {
 	cmd := exec.Command("docker-compose", "-f", filePath, "up", "-d")
@@ -25,3 +29,24 @@ func Rmf(filePath string) (string, error) {
 	stdout, err := cmd.CombinedOutput()
 	return string(stdout), err
 }
+
+func GetComposeProject(yml []byte, env map[string]string) (*types.Project, error) {
+	var configFiles []types.ConfigFile
+	configFiles = append(configFiles, types.ConfigFile{
+		Filename: "docker-compose.yml",
+		Content:  yml},
+	)
+	details := types.ConfigDetails{
+		WorkingDir:  "",
+		ConfigFiles: configFiles,
+		Environment: env,
+	}
+
+	project, err := loader.Load(details, func(options *loader.Options) {
+
+	})
+	if err != nil {
+		return nil, err
+	}
+	return project, nil
+}
diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts
index 14f1c77a7..ccffbfb6d 100644
--- a/frontend/src/api/interface/app.ts
+++ b/frontend/src/api/interface/app.ts
@@ -64,7 +64,6 @@ export namespace App {
 
     export interface AppInstalled extends CommonModel {
         name: string;
-        version: string;
         appId: string;
         appDetailId: string;
         params: string;
@@ -73,7 +72,7 @@ export namespace App {
         message: string;
         appName: string;
         icon: string;
-        constainers: any[];
+        app: App;
     }
 
     export interface AppInstalledOp {