diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index b1c91beef6..2686dfb3cf 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -351,9 +351,11 @@ var migrations = []Migration{
 	// v198 -> v199
 	NewMigration("Add issue content history table", addTableIssueContentHistory),
 	// v199 -> v200
-	NewMigration("Add remote version table", addRemoteVersionTable),
+	NewMigration("No-op (remote version is using AppState now)", addRemoteVersionTableNoop),
 	// v200 -> v201
 	NewMigration("Add table app_state", addTableAppState),
+	// v201 -> v202
+	NewMigration("Drop table remote_version (if exists)", dropTableRemoteVersion),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v199.go b/models/migrations/v199.go
index 64b21172c1..4351ba4fa8 100644
--- a/models/migrations/v199.go
+++ b/models/migrations/v199.go
@@ -5,19 +5,10 @@
 package migrations
 
 import (
-	"fmt"
-
 	"xorm.io/xorm"
 )
 
-func addRemoteVersionTable(x *xorm.Engine) error {
-	type RemoteVersion struct {
-		ID      int64  `xorm:"pk autoincr"`
-		Version string `xorm:"VARCHAR(50)"`
-	}
-
-	if err := x.Sync2(new(RemoteVersion)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
-	}
+func addRemoteVersionTableNoop(x *xorm.Engine) error {
+	// we used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
 	return nil
 }
diff --git a/models/migrations/v201.go b/models/migrations/v201.go
new file mode 100644
index 0000000000..637c30617c
--- /dev/null
+++ b/models/migrations/v201.go
@@ -0,0 +1,15 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"xorm.io/xorm"
+)
+
+func dropTableRemoteVersion(x *xorm.Engine) error {
+	// drop the orphaned table introduced in `v199`, now the update checker also uses AppState, do not need this table
+	_ = x.DropTables("remote_version")
+	return nil
+}
diff --git a/modules/cron/tasks_extended.go b/modules/cron/tasks_extended.go
index 6645e71d2c..9a37c40faf 100644
--- a/modules/cron/tasks_extended.go
+++ b/modules/cron/tasks_extended.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/models"
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/updatechecker"
 )
 
 func registerDeleteInactiveUsers() {
@@ -145,7 +146,7 @@ func registerUpdateGiteaChecker() {
 		HTTPEndpoint: "https://dl.gitea.io/gitea/version.json",
 	}, func(ctx context.Context, _ *models.User, config Config) error {
 		updateCheckerConfig := config.(*UpdateCheckerConfig)
-		return models.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint)
+		return updatechecker.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint)
 	})
 }
 
diff --git a/models/update_checker.go b/modules/updatechecker/update_checker.go
similarity index 56%
rename from models/update_checker.go
rename to modules/updatechecker/update_checker.go
index 5b4fce69ec..01242189fa 100644
--- a/models/update_checker.go
+++ b/modules/updatechecker/update_checker.go
@@ -2,29 +2,28 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package updatechecker
 
 import (
 	"encoding/json"
-	"fmt"
 	"io/ioutil"
 	"net/http"
 
-	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/appstate"
 	"code.gitea.io/gitea/modules/proxy"
 	"code.gitea.io/gitea/modules/setting"
 
 	"github.com/hashicorp/go-version"
 )
 
-// RemoteVersion stores the remote version from the JSON endpoint
-type RemoteVersion struct {
-	ID      int64  `xorm:"pk autoincr"`
-	Version string `xorm:"VARCHAR(50)"`
+// CheckerState stores the remote version from the JSON endpoint
+type CheckerState struct {
+	LatestVersion string
 }
 
-func init() {
-	db.RegisterModel(new(RemoteVersion))
+// Name returns the name of the state item for update checker
+func (r *CheckerState) Name() string {
+	return "update-checker"
 }
 
 // GiteaUpdateChecker returns error when new version of Gitea is available
@@ -49,60 +48,33 @@ func GiteaUpdateChecker(httpEndpoint string) error {
 		return err
 	}
 
-	type v struct {
+	type respType struct {
 		Latest struct {
 			Version string `json:"version"`
 		} `json:"latest"`
 	}
-	ver := v{}
-	err = json.Unmarshal(body, &ver)
+	respData := respType{}
+	err = json.Unmarshal(body, &respData)
 	if err != nil {
 		return err
 	}
 
-	return UpdateRemoteVersion(ver.Latest.Version)
+	return UpdateRemoteVersion(respData.Latest.Version)
 
 }
 
 // UpdateRemoteVersion updates the latest available version of Gitea
 func UpdateRemoteVersion(version string) (err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
-		return err
-	}
-
-	currentVersion := &RemoteVersion{ID: 1}
-	has, err := sess.Get(currentVersion)
-	if err != nil {
-		return fmt.Errorf("get: %v", err)
-	} else if !has {
-		currentVersion.ID = 1
-		currentVersion.Version = version
-
-		if _, err = sess.InsertOne(currentVersion); err != nil {
-			return fmt.Errorf("insert: %v", err)
-		}
-		return nil
-	}
-
-	if _, err = sess.Update(&RemoteVersion{ID: 1, Version: version}); err != nil {
-		return err
-	}
-
-	return sess.Commit()
+	return appstate.AppState.Set(&CheckerState{LatestVersion: version})
 }
 
 // GetRemoteVersion returns the current remote version (or currently installed verson if fail to fetch from DB)
 func GetRemoteVersion() string {
-	e := db.GetEngine(db.DefaultContext)
-	v := &RemoteVersion{ID: 1}
-	_, err := e.Get(&v)
-	if err != nil {
-		// return current version if fail to fetch from DB
-		return setting.AppVer
+	item := new(CheckerState)
+	if err := appstate.AppState.Get(item); err != nil {
+		return ""
 	}
-	return v.Version
+	return item.LatestVersion
 }
 
 // GetNeedUpdate returns true whether a newer version of Gitea is available
@@ -112,7 +84,12 @@ func GetNeedUpdate() bool {
 		// return false to fail silently
 		return false
 	}
-	remoteVer, err := version.NewVersion(GetRemoteVersion())
+	remoteVerStr := GetRemoteVersion()
+	if remoteVerStr == "" {
+		// no remote version is known
+		return false
+	}
+	remoteVer, err := version.NewVersion(remoteVerStr)
 	if err != nil {
 		// return false to fail silently
 		return false
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index ca5b157523..223114dae1 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -26,6 +26,7 @@ import (
 	"code.gitea.io/gitea/modules/queue"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/updatechecker"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/forms"
 	"code.gitea.io/gitea/services/mailer"
@@ -125,8 +126,8 @@ func Dashboard(ctx *context.Context) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminDashboard"] = true
 	ctx.Data["Stats"] = models.GetStatistic()
-	ctx.Data["NeedUpdate"] = models.GetNeedUpdate()
-	ctx.Data["RemoteVersion"] = models.GetRemoteVersion()
+	ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate()
+	ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion()
 	// FIXME: update periodically
 	updateSystemStatus()
 	ctx.Data["SysStatus"] = sysStatus