diff --git a/cmd/delete.go b/cmd/delete.go
index 2975cfb298d2aa786dfb81a1a26caf694f5e5361..7e84c22b316305abd74ff7749b2e53be1f74e8c3 100644
--- a/cmd/delete.go
+++ b/cmd/delete.go
@@ -2,6 +2,7 @@ package cmd
 
 import (
 	"fmt"
+	"sort"
 
 	"github.com/golang/glog"
 	"github.com/spf13/cobra"
@@ -44,7 +45,7 @@ var deleteCmd = &cobra.Command{
 
 		defaultNs, _, err := clientConfig.Namespace()
 
-		utils.SortDepLast(objs)
+		sort.Sort(sort.Reverse(utils.DependencyOrder(objs)))
 
 		deleteOpts := v1.DeleteOptions{OrphanDependents: &boolFalse}
 		if gracePeriod >= 0 {
diff --git a/cmd/diff.go b/cmd/diff.go
new file mode 100644
index 0000000000000000000000000000000000000000..61128acadb804bb2d1db2972917c46e83625f6aa
--- /dev/null
+++ b/cmd/diff.go
@@ -0,0 +1,96 @@
+package cmd
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"sort"
+
+	"github.com/golang/glog"
+	"github.com/mattn/go-isatty"
+	"github.com/spf13/cobra"
+	"github.com/yudai/gojsondiff"
+	"github.com/yudai/gojsondiff/formatter"
+	"k8s.io/client-go/pkg/api/errors"
+
+	"github.com/ksonnet/kubecfg/utils"
+)
+
+func init() {
+	RootCmd.AddCommand(diffCmd)
+}
+
+var diffCmd = &cobra.Command{
+	Use:   "diff",
+	Short: "Display differences between server and local config",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		out := cmd.OutOrStdout()
+
+		objs, err := readObjs(cmd, args)
+		if err != nil {
+			return err
+		}
+
+		clientpool, disco, err := restClientPool(cmd)
+		if err != nil {
+			return err
+		}
+
+		defaultNs, _, err := clientConfig.Namespace()
+		if err != nil {
+			return err
+		}
+
+		sort.Sort(utils.AlphabeticalOrder(objs))
+
+		for _, obj := range objs {
+			desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj))
+			glog.V(2).Info("Fetching ", desc)
+
+			c, err := clientForResource(clientpool, disco, obj, defaultNs)
+			if err != nil {
+				return err
+			}
+
+			liveObj, err := c.Get(obj.GetName())
+			if err != nil && errors.IsNotFound(err) {
+				glog.V(2).Infof("%s doesn't exist on the server", desc)
+				liveObj = nil
+			} else if err != nil {
+				return fmt.Errorf("Error fetching %s: %v", desc, err)
+			}
+
+			fmt.Fprintln(out, "---")
+			fmt.Fprintf(out, "- live %s\n+ config %s", desc, desc)
+			if liveObj == nil {
+				fmt.Fprintf(out, "%s doesn't exist on server\n", desc)
+				continue
+			}
+
+			diff := gojsondiff.New().CompareObjects(liveObj.Object, obj.Object)
+
+			if diff.Modified() {
+				fcfg := formatter.AsciiFormatterConfig{
+					Coloring: istty(out),
+				}
+				formatter := formatter.NewAsciiFormatter(liveObj.Object, fcfg)
+				text, err := formatter.Format(diff)
+				if err != nil {
+					return err
+				}
+				fmt.Fprintf(out, "%s", text)
+			} else {
+				fmt.Fprintf(out, "%s unchanged\n", desc)
+			}
+		}
+
+		return nil
+	},
+}
+
+func istty(w io.Writer) bool {
+	if f, ok := w.(*os.File); ok {
+		return isatty.IsTerminal(f.Fd())
+	}
+	return false
+}
diff --git a/cmd/update.go b/cmd/update.go
index 655afe59aaec2811d8aa2b56ed72d3553cc0369b..a2ade46c24963eea14fde0706842ea8ab1c8b137 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -3,6 +3,7 @@ package cmd
 import (
 	"encoding/json"
 	"fmt"
+	"sort"
 
 	"github.com/golang/glog"
 	"github.com/spf13/cobra"
@@ -49,7 +50,7 @@ var updateCmd = &cobra.Command{
 			return err
 		}
 
-		utils.SortDepFirst(objs)
+		sort.Sort(utils.DependencyOrder(objs))
 
 		for _, obj := range objs {
 			desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj))
diff --git a/utils/sort.go b/utils/sort.go
index 3f92be0720a5a28740152029b2f047c3758b710b..2fd88276afb44a9de8773ad1dcf0677500288407 100644
--- a/utils/sort.go
+++ b/utils/sort.go
@@ -1,8 +1,6 @@
 package utils
 
 import (
-	"sort"
-
 	"k8s.io/client-go/pkg/api/unversioned"
 	"k8s.io/client-go/pkg/runtime"
 )
@@ -42,23 +40,32 @@ func depTier(o *runtime.Unstructured) int {
 	}
 }
 
-type dependentObjects []*runtime.Unstructured
+// DependencyOrder is a `sort.Interface` that *best-effort* sorts the
+// objects so that known dependencies appear earlier in the list.  The
+// idea is to prevent *some* of the "crash-restart" loops when
+// creating inter-dependent resources.
+type DependencyOrder []*runtime.Unstructured
 
-func (l dependentObjects) Len() int      { return len(l) }
-func (l dependentObjects) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
-func (l dependentObjects) Less(i, j int) bool {
+func (l DependencyOrder) Len() int      { return len(l) }
+func (l DependencyOrder) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l DependencyOrder) Less(i, j int) bool {
 	return depTier(l[i]) < depTier(l[j])
 }
 
-// SortDepFirst *best-effort* sorts the objects so that known
-// dependencies appear earlier in the list.  The idea is to prevent
-// *some* of the "crash-restart" loops when creating inter-dependent
-// resources.
-func SortDepFirst(objs []*runtime.Unstructured) {
-	sort.Sort(dependentObjects(objs))
-}
+// AlphabeticalOrder is a `sort.Interface` that sorts the
+// objects by namespace/name/kind alphabetical order
+type AlphabeticalOrder []*runtime.Unstructured
+
+func (l AlphabeticalOrder) Len() int      { return len(l) }
+func (l AlphabeticalOrder) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l AlphabeticalOrder) Less(i, j int) bool {
+	a, b := l[i], l[j]
 
-// SortDepLast is the reverse order of SortDepFirst.
-func SortDepLast(objs []*runtime.Unstructured) {
-	sort.Sort(sort.Reverse(dependentObjects(objs)))
+	if a.GetNamespace() != b.GetNamespace() {
+		return a.GetNamespace() < b.GetNamespace()
+	}
+	if a.GetName() != b.GetName() {
+		return a.GetName() < b.GetName()
+	}
+	return a.GetKind() < b.GetKind()
 }
diff --git a/utils/sort_test.go b/utils/sort_test.go
index c579badc1d220122912ec0a194fb27ef952473c6..0a8df52de984d0ab8f22de5d1684856681df9174 100644
--- a/utils/sort_test.go
+++ b/utils/sort_test.go
@@ -1,21 +1,25 @@
 package utils
 
 import (
+	"reflect"
+	"sort"
 	"testing"
 
 	"k8s.io/client-go/pkg/runtime"
 )
 
-func newObj(apiVersion, kind string) *runtime.Unstructured {
-	return &runtime.Unstructured{
-		Object: map[string]interface{}{
-			"apiVersion": apiVersion,
-			"kind":       kind,
-		},
+var _ sort.Interface = DependencyOrder{}
+
+func TestDepSort(t *testing.T) {
+	newObj := func(apiVersion, kind string) *runtime.Unstructured {
+		return &runtime.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": apiVersion,
+				"kind":       kind,
+			},
+		}
 	}
-}
 
-func TestSort(t *testing.T) {
 	objs := []*runtime.Unstructured{
 		newObj("extensions/v1beta1", "Deployment"),
 		newObj("v1", "ConfigMap"),
@@ -23,7 +27,7 @@ func TestSort(t *testing.T) {
 		newObj("v1", "Service"),
 	}
 
-	SortDepFirst(objs)
+	sort.Sort(DependencyOrder(objs))
 
 	if objs[0].GetKind() != "Namespace" {
 		t.Error("Namespace should be sorted first")
@@ -32,3 +36,35 @@ func TestSort(t *testing.T) {
 		t.Error("Deployment should be sorted after other objects")
 	}
 }
+
+func TestAlphaSort(t *testing.T) {
+	newObj := func(ns, name, kind string) *runtime.Unstructured {
+		o := runtime.Unstructured{}
+		o.SetNamespace(ns)
+		o.SetName(name)
+		o.SetKind(kind)
+		return &o
+	}
+
+	objs := []*runtime.Unstructured{
+		newObj("default", "mysvc", "Deployment"),
+		newObj("", "default", "StorageClass"),
+		newObj("", "default", "ClusterRole"),
+		newObj("default", "mydeploy", "Deployment"),
+		newObj("default", "mysvc", "Secret"),
+	}
+
+	expected := []*runtime.Unstructured{
+		objs[2],
+		objs[1],
+		objs[3],
+		objs[0],
+		objs[4],
+	}
+
+	sort.Sort(AlphabeticalOrder(objs))
+
+	if !reflect.DeepEqual(objs, expected) {
+		t.Errorf("actual != expected: %v != %v", objs, expected)
+	}
+}