diff --git a/cli/catalogue/catalogue.go b/cli/catalogue/catalogue.go
index 8bb74c0131..6138d3b1f4 100644
--- a/cli/catalogue/catalogue.go
+++ b/cli/catalogue/catalogue.go
@@ -14,6 +14,7 @@ import (
 	"coopcloud.tech/abra/pkg/limit"
 	"coopcloud.tech/abra/pkg/recipe"
 	"github.com/AlecAivazis/survey/v2"
+	"github.com/go-git/go-git/v5"
 	"github.com/sirupsen/logrus"
 	"github.com/urfave/cli/v2"
 )
@@ -154,6 +155,7 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass
 			catl[recipeMeta.Name] = recipe.RecipeMeta{
 				Name:          recipeMeta.Name,
 				Repository:    recipeMeta.CloneURL,
+				SSHURL:        recipeMeta.SSHURL,
 				Icon:          recipeMeta.AvatarURL,
 				DefaultBranch: recipeMeta.DefaultBranch,
 				Description:   recipeMeta.Description,
@@ -212,7 +214,17 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass
 			}
 
 			if internal.Push {
-				if err := gitPkg.Push(cataloguePath, false); err != nil {
+				repo, err := git.PlainOpen(cataloguePath)
+				if err != nil {
+					logrus.Fatal(err)
+				}
+
+				sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, "recipes")
+				if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil {
+					logrus.Fatal(err)
+				}
+
+				if err := gitPkg.Push(cataloguePath, "origin-ssh", false, internal.Dry); err != nil {
 					logrus.Fatal(err)
 				}
 			}
diff --git a/cli/recipe/release.go b/cli/recipe/release.go
index dbda67e4dd..723a4052ee 100644
--- a/cli/recipe/release.go
+++ b/cli/recipe/release.go
@@ -213,7 +213,7 @@ func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string
 		logrus.Fatal(err)
 	}
 
-	if err := pushRelease(recipe.Dir()); err != nil {
+	if err := pushRelease(recipe); err != nil {
 		logrus.Fatal(err)
 	}
 
@@ -308,7 +308,7 @@ func tagRelease(tagString string, repo *git.Repository) error {
 	return nil
 }
 
-func pushRelease(recipeDir string) error {
+func pushRelease(recipe recipe.Recipe) error {
 	if internal.Dry {
 		logrus.Info("dry run: no changes pushed")
 		return nil
@@ -325,7 +325,7 @@ func pushRelease(recipeDir string) error {
 	}
 
 	if internal.Push {
-		if err := gitPkg.Push(recipeDir, true); err != nil {
+		if err := recipe.Push(internal.Dry); err != nil {
 			return err
 		}
 	}
@@ -403,7 +403,7 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
 		logrus.Fatal(err)
 	}
 
-	if err := pushRelease(recipe.Dir()); err != nil {
+	if err := pushRelease(recipe); err != nil {
 		logrus.Fatal(err)
 	}
 
diff --git a/pkg/config/env.go b/pkg/config/env.go
index 6cc8a2089b..00e7c45cb8 100644
--- a/pkg/config/env.go
+++ b/pkg/config/env.go
@@ -20,6 +20,7 @@ var RECIPES_DIR = path.Join(ABRA_DIR, "apps")
 var VENDOR_DIR = path.Join(ABRA_DIR, "vendor")
 var RECIPES_JSON = path.Join(ABRA_DIR, "catalogue", "recipes.json")
 var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
+var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
 
 // GetServers retrieves all servers.
 func GetServers() ([]string, error) {
diff --git a/pkg/git/push.go b/pkg/git/push.go
index cb8378f5f2..d0eeae9d7a 100644
--- a/pkg/git/push.go
+++ b/pkg/git/push.go
@@ -1,36 +1,41 @@
 package git
 
 import (
-	"path"
-
-	configPkg "coopcloud.tech/abra/pkg/config"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/sirupsen/logrus"
 )
 
-// Push pushes the latest changes
-func Push(recipeName string, tags bool) error {
-	recipeDir := path.Join(configPkg.RECIPES_DIR, recipeName)
-	commitRepo, err := git.PlainOpen(recipeDir)
+// Push pushes the latest changes & optionally tags to the default remote
+func Push(repoDir string, remote string, tags bool, dryRun bool) error {
+	if dryRun {
+		logrus.Infof("dry run: no git changes pushed in %s", repoDir)
+		return nil
+	}
+
+	commitRepo, err := git.PlainOpen(repoDir)
 	if err != nil {
 		return err
 	}
 
-	if err := commitRepo.Push(&git.PushOptions{}); err != nil {
+	opts := &git.PushOptions{}
+	if remote != "" {
+		opts.RemoteName = remote
+	}
+
+	if err := commitRepo.Push(opts); err != nil {
 		return err
 	}
+
 	logrus.Info("git changes pushed")
 
 	if tags {
-		pushOpts := &git.PushOptions{
-			RefSpecs: []config.RefSpec{
-				config.RefSpec("+refs/tags/*:refs/tags/*"),
-			},
-		}
-		if err := commitRepo.Push(pushOpts); err != nil {
+		opts.RefSpecs = append(opts.RefSpecs, config.RefSpec("+refs/tags/*:refs/tags/*"))
+
+		if err := commitRepo.Push(opts); err != nil {
 			return err
 		}
+
 		logrus.Info("git tags pushed")
 	}
 
diff --git a/pkg/git/remote.go b/pkg/git/remote.go
new file mode 100644
index 0000000000..81659816f9
--- /dev/null
+++ b/pkg/git/remote.go
@@ -0,0 +1,28 @@
+package git
+
+import (
+	"strings"
+
+	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/config"
+	"github.com/sirupsen/logrus"
+)
+
+// CreateRemote creates a new git remote in a repository
+func CreateRemote(repo *git.Repository, name, url string, dryRun bool) error {
+	if dryRun {
+		logrus.Infof("dry run: remote %s (%s) not created", name, url)
+		return nil
+	}
+
+	if _, err := repo.CreateRemote(&config.RemoteConfig{
+		Name: name,
+		URLs: []string{url},
+	}); err != nil {
+		if !strings.Contains(err.Error(), "remote already exists") {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go
index 2ea5ac6b5d..eff3d971b8 100644
--- a/pkg/recipe/recipe.go
+++ b/pkg/recipe/recipe.go
@@ -56,6 +56,7 @@ type RecipeMeta struct {
 	Icon          string         `json:"icon"`
 	Name          string         `json:"name"`
 	Repository    string         `json:"repository"`
+	SSHURL        string         `json:"ssh_url"`
 	Versions      RecipeVersions `json:"versions"`
 	Website       string         `json:"website"`
 }
@@ -128,6 +129,25 @@ type Recipe struct {
 	Meta   RecipeMeta
 }
 
+// Push pushes the latest changes to a SSH URL remote. You need to have your
+// local SSH configuration for git.coopcloud.tech working for this to work
+func (r Recipe) Push(dryRun bool) error {
+	repo, err := git.PlainOpen(r.Dir())
+	if err != nil {
+		return err
+	}
+
+	if err := gitPkg.CreateRemote(repo, "origin-ssh", r.Meta.SSHURL, dryRun); err != nil {
+		return err
+	}
+
+	if err := gitPkg.Push(r.Dir(), "origin-ssh", true, dryRun); err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // Dir retrieves the recipe repository path
 func (r Recipe) Dir() string {
 	return path.Join(config.RECIPES_DIR, r.Name)