diff --git a/.golangci.yml b/.golangci.yml
index b04683b7ba..5480b02bbf 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -28,6 +28,7 @@ linters:
     - misspell
     - prealloc
     - unparam
+    - nakedret
 linters-settings:
   errcheck:
     check-blank: false
diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go
index 9d222e44d5..16a54e5783 100644
--- a/cmd/podman/containers/run.go
+++ b/cmd/podman/containers/run.go
@@ -122,7 +122,7 @@ func run(cmd *cobra.Command, args []string) error {
 		return nil
 	}
 	if runRmi {
-		_, err := registry.ImageEngine().Delete(registry.GetContext(), []string{args[0]}, entities.ImageDeleteOptions{})
+		_, err := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{})
 		if err != nil {
 			logrus.Errorf("%s", errors.Wrapf(err, "failed removing image"))
 		}
diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go
index 135fda387f..da6a90d2b2 100644
--- a/cmd/podman/images/rm.go
+++ b/cmd/podman/images/rm.go
@@ -2,7 +2,6 @@ package images
 
 import (
 	"fmt"
-	"os"
 
 	"github.com/containers/libpod/cmd/podman/registry"
 	"github.com/containers/libpod/pkg/domain/entities"
@@ -23,7 +22,7 @@ var (
   podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
 	}
 
-	imageOpts = entities.ImageDeleteOptions{}
+	imageOpts = entities.ImageRemoveOptions{}
 )
 
 func init() {
@@ -40,32 +39,25 @@ func imageRemoveFlagSet(flags *pflag.FlagSet) {
 	flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images")
 	flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image")
 }
-func rm(cmd *cobra.Command, args []string) error {
 
+func rm(cmd *cobra.Command, args []string) error {
 	if len(args) < 1 && !imageOpts.All {
 		return errors.Errorf("image name or ID must be specified")
 	}
 	if len(args) > 0 && imageOpts.All {
 		return errors.Errorf("when using the --all switch, you may not pass any images names or IDs")
 	}
-	report, err := registry.ImageEngine().Delete(registry.GetContext(), args, imageOpts)
-	if err != nil {
-		switch {
-		case report != nil && report.ImageNotFound != nil:
-			fmt.Fprintln(os.Stderr, err.Error())
-			registry.SetExitCode(2)
-		case report != nil && report.ImageInUse != nil:
-			fmt.Fprintln(os.Stderr, err.Error())
-		default:
-			return err
+
+	report, err := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts)
+	if report != nil {
+		for _, u := range report.Untagged {
+			fmt.Println("Untagged: " + u)
 		}
+		for _, d := range report.Deleted {
+			fmt.Println("Deleted: " + d)
+		}
+		registry.SetExitCode(report.ExitCode)
 	}
 
-	for _, u := range report.Untagged {
-		fmt.Println("Untagged: " + u)
-	}
-	for _, d := range report.Deleted {
-		fmt.Println("Deleted: " + d)
-	}
-	return nil
+	return err
 }
diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go
index e09107b53f..f4b91dd781 100644
--- a/cmd/podman/system/service.go
+++ b/cmd/podman/system/service.go
@@ -90,7 +90,8 @@ func service(cmd *cobra.Command, args []string) error {
 
 	logrus.Warn("This function is EXPERIMENTAL")
 	fmt.Fprintf(os.Stderr, "This function is EXPERIMENTAL.\n")
-	return registry.ContainerEngine().RestService(registry.GetContext(), opts)
+
+	return restService(opts, cmd.Flags(), registry.PodmanConfig())
 }
 
 func resolveApiURI(_url []string) (string, error) {
diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go
new file mode 100644
index 0000000000..3da6ccfc7d
--- /dev/null
+++ b/cmd/podman/system/service_abi.go
@@ -0,0 +1,57 @@
+// +build ABISupport
+
+package system
+
+import (
+	"context"
+	"net"
+	"strings"
+
+	api "github.com/containers/libpod/pkg/api/server"
+	"github.com/containers/libpod/pkg/domain/entities"
+	"github.com/containers/libpod/pkg/domain/infra"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/pflag"
+)
+
+func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
+	var (
+		listener *net.Listener
+		err      error
+	)
+
+	if opts.URI != "" {
+		fields := strings.Split(opts.URI, ":")
+		if len(fields) == 1 {
+			return errors.Errorf("%s is an invalid socket destination", opts.URI)
+		}
+		address := strings.Join(fields[1:], ":")
+		l, err := net.Listen(fields[0], address)
+		if err != nil {
+			return errors.Wrapf(err, "unable to create socket %s", opts.URI)
+		}
+		listener = &l
+	}
+
+	rt, err := infra.GetRuntime(context.Background(), flags, cfg)
+	if err != nil {
+		return err
+	}
+
+	server, err := api.NewServerWithSettings(rt, opts.Timeout, listener)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if err := server.Shutdown(); err != nil {
+			logrus.Warnf("Error when stopping API service: %s", err)
+		}
+	}()
+
+	err = server.Serve()
+	if listener != nil {
+		_ = (*listener).Close()
+	}
+	return err
+}
diff --git a/cmd/podman/system/service_unsupported.go b/cmd/podman/system/service_unsupported.go
new file mode 100644
index 0000000000..95f8189f69
--- /dev/null
+++ b/cmd/podman/system/service_unsupported.go
@@ -0,0 +1,14 @@
+// +build !ABISupport
+
+package system
+
+import (
+	"errors"
+
+	"github.com/containers/libpod/pkg/domain/entities"
+	"github.com/spf13/pflag"
+)
+
+func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
+	return errors.New("not supported")
+}
diff --git a/hack/golangci-lint.sh b/hack/golangci-lint.sh
index 385b21f39f..f4e60d8f50 100755
--- a/hack/golangci-lint.sh
+++ b/hack/golangci-lint.sh
@@ -3,13 +3,22 @@
 # Need to run linter twice to cover all the build tags code paths
 
 declare -A BUILD_TAGS
+# TODO: add systemd tag
 BUILD_TAGS[default]="apparmor,seccomp,selinux"
 BUILD_TAGS[abi]="${BUILD_TAGS[default]},ABISupport,varlink,!remoteclient"
-BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},!ABISupport,!varlink,remoteclient"
+BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},!ABISupport,varlink,remoteclient"
+
+declare -A SKIP_DIRS
+SKIP_DIRS[abi]=""
+# TODO: add "ABISupport" build tag to pkg/api
+SKIP_DIRS[tunnel]="pkg/api"
 
 [[ $1 == run ]] && shift
 
 for i in tunnel abi; do
-  echo Build Tags: ${BUILD_TAGS[$i]}
-  golangci-lint run --build-tags=${BUILD_TAGS[$i]} "$@"
+  echo ""
+  echo Running golangci-lint for "$i"
+  echo Build Tags          "$i": ${BUILD_TAGS[$i]}
+  echo Skipped directories "$i": ${SKIP_DIRS[$i]}
+  golangci-lint run --build-tags=${BUILD_TAGS[$i]} --skip-dirs=${SKIP_DIRS[$i]} "$@"
 done
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 8ef32716dd..7ebfb0d1e2 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -6,8 +6,8 @@ import (
 
 	"github.com/containers/libpod/libpod"
 	"github.com/containers/libpod/libpod/events"
-	"github.com/containers/libpod/pkg/api/handlers"
 	"github.com/containers/libpod/pkg/api/handlers/utils"
+	"github.com/containers/libpod/pkg/domain/entities"
 	"github.com/gorilla/schema"
 	jsoniter "github.com/json-iterator/go"
 	"github.com/pkg/errors"
@@ -70,7 +70,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
 	coder.SetEscapeHTML(true)
 
 	for event := range eventChannel {
-		e := handlers.EventToApiEvent(event)
+		e := entities.ConvertToEntitiesEvent(*event)
 		if err := coder.Encode(e); err != nil {
 			logrus.Errorf("unable to write json: %q", err)
 		}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 284b336377..46401e4f26 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -22,6 +22,7 @@ import (
 	"github.com/containers/libpod/pkg/api/handlers"
 	"github.com/containers/libpod/pkg/api/handlers/utils"
 	"github.com/containers/libpod/pkg/domain/entities"
+	"github.com/containers/libpod/pkg/domain/infra/abi"
 	"github.com/containers/libpod/pkg/util"
 	utils2 "github.com/containers/libpod/utils"
 	"github.com/gorilla/schema"
@@ -698,3 +699,30 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
 
 	utils.WriteResponse(w, http.StatusOK, reports)
 }
+
+// ImagesRemove is the endpoint for image removal.
+func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+	runtime := r.Context().Value("runtime").(*libpod.Runtime)
+	decoder := r.Context().Value("decoder").(*schema.Decoder)
+	query := struct {
+		All    bool     `schema:"all"`
+		Force  bool     `schema:"force"`
+		Images []string `schema:"images"`
+	}{
+		All:   false,
+		Force: false,
+	}
+
+	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+		return
+	}
+
+	opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
+
+	imageEngine := abi.ImageEngine{Libpod: runtime}
+	rmReport, rmError := imageEngine.Remove(r.Context(), query.Images, opts)
+	report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Error: rmError.Error()}
+	utils.WriteResponse(w, http.StatusOK, report)
+}
diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go
index ba97a4755d..87891d4a8c 100644
--- a/pkg/api/handlers/swagger/swagger.go
+++ b/pkg/api/handlers/swagger/swagger.go
@@ -49,6 +49,13 @@ type swagLibpodImagesPullResponse struct {
 	Body handlers.LibpodImagesPullReport
 }
 
+// Remove response
+// swagger:response DocsLibpodImagesRemoveResponse
+type swagLibpodImagesRemoveResponse struct {
+	// in:body
+	Body handlers.LibpodImagesRemoveReport
+}
+
 // Delete response
 // swagger:response DocsImageDeleteResponse
 type swagImageDeleteResponse struct {
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 4c5e384370..58a12ea6ae 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -4,16 +4,13 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"strconv"
 	"time"
 
 	"github.com/containers/image/v5/manifest"
-	"github.com/containers/libpod/libpod/events"
 	libpodImage "github.com/containers/libpod/libpod/image"
 	"github.com/containers/libpod/pkg/domain/entities"
 	docker "github.com/docker/docker/api/types"
 	dockerContainer "github.com/docker/docker/api/types/container"
-	dockerEvents "github.com/docker/docker/api/types/events"
 	dockerNetwork "github.com/docker/docker/api/types/network"
 	"github.com/docker/go-connections/nat"
 	"github.com/pkg/errors"
@@ -39,6 +36,14 @@ type LibpodImagesPullReport struct {
 	ID string `json:"id"`
 }
 
+// LibpodImagesRemoveReport is the return type for image removal via the rest
+// api.
+type LibpodImagesRemoveReport struct {
+	entities.ImageRemoveReport
+	// Image removal requires is to return data and an error.
+	Error string
+}
+
 type ContainersPruneReport struct {
 	docker.ContainersPruneReport
 }
@@ -143,10 +148,6 @@ type PodCreateConfig struct {
 	Share        string   `json:"share"`
 }
 
-type Event struct {
-	dockerEvents.Message
-}
-
 type HistoryResponse struct {
 	ID        string   `json:"Id"`
 	Created   int64    `json:"Created"`
@@ -173,49 +174,6 @@ type ExecCreateResponse struct {
 	docker.IDResponse
 }
 
-func (e *Event) ToLibpodEvent() *events.Event {
-	exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"])
-	if err != nil {
-		return nil
-	}
-	status, err := events.StringToStatus(e.Action)
-	if err != nil {
-		return nil
-	}
-	t, err := events.StringToType(e.Type)
-	if err != nil {
-		return nil
-	}
-	lp := events.Event{
-		ContainerExitCode: exitCode,
-		ID:                e.Actor.ID,
-		Image:             e.Actor.Attributes["image"],
-		Name:              e.Actor.Attributes["name"],
-		Status:            status,
-		Time:              time.Unix(e.Time, e.TimeNano),
-		Type:              t,
-	}
-	return &lp
-}
-
-func EventToApiEvent(e *events.Event) *Event {
-	return &Event{dockerEvents.Message{
-		Type:   e.Type.String(),
-		Action: e.Status.String(),
-		Actor: dockerEvents.Actor{
-			ID: e.ID,
-			Attributes: map[string]string{
-				"image":             e.Image,
-				"name":              e.Name,
-				"containerExitCode": strconv.Itoa(e.ContainerExitCode),
-			},
-		},
-		Scope:    "local",
-		Time:     e.Time.Unix(),
-		TimeNano: e.Time.UnixNano(),
-	}}
-}
-
 func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
 	containers, err := l.Containers()
 	if err != nil {
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 6cc6f0cfac..f59dca6f57 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -822,6 +822,38 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
 	//   500:
 	//     $ref: '#/responses/InternalError'
 	r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost)
+	// swagger:operation GET /libpod/images/remove libpod libpodImagesRemove
+	// ---
+	// tags:
+	//  - images
+	// summary: Remove one or more images from the storage.
+	// description: Remove one or more images from the storage.
+	// parameters:
+	//   - in: query
+	//     name: images
+	//     description: Images IDs or names to remove.
+	//     type: array
+	//     items:
+	//        type: string
+	//   - in: query
+	//     name: all
+	//     description: Remove all images.
+	//     type: boolean
+	//     default: true
+	//   - in: query
+	//     name: force
+	//     description: Force image removal (including containers using the images).
+	//     type: boolean
+	// produces:
+	// - application/json
+	// responses:
+	//   200:
+	//     $ref: "#/responses/DocsLibpodImagesRemoveResponse"
+	//   400:
+	//     $ref: "#/responses/BadParamError"
+	//   500:
+	//     $ref: '#/responses/InternalError'
+	r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodGet)
 	// swagger:operation POST /libpod/images/pull libpod libpodImagesPull
 	// ---
 	// tags:
diff --git a/pkg/api/types/types.go b/pkg/api/types/types.go
new file mode 100644
index 0000000000..1b91364e3b
--- /dev/null
+++ b/pkg/api/types/types.go
@@ -0,0 +1,9 @@
+package types
+
+const (
+	// DefaultAPIVersion is the version of the API the server defaults to.
+	DefaultAPIVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
+
+	// DefaultAPIVersion is the minimal required version of the API.
+	MinimalAPIVersion = "1.24"
+)
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index 29b6f04ec1..da3755fc87 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -15,7 +15,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/containers/libpod/pkg/api/handlers"
+	"github.com/containers/libpod/pkg/api/types"
 	jsoniter "github.com/json-iterator/go"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
@@ -27,7 +27,7 @@ var (
 	basePath = &url.URL{
 		Scheme: "http",
 		Host:   "d",
-		Path:   "/v" + handlers.MinimalApiVersion + "/libpod",
+		Path:   "/v" + types.MinimalAPIVersion + "/libpod",
 	}
 )
 
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index e0f523ebd3..06f01c7a0c 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -109,23 +109,34 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe
 	return &report, response.Process(&report)
 }
 
-// Remove deletes an image from local storage.  The optional force parameter will forcibly remove
-// the image by removing all all containers, including those that are Running, first.
-func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) {
-	var deletes []map[string]string
+// Remove deletes an image from local storage.  The optional force parameter
+// will forcibly remove the image by removing all all containers, including
+// those that are Running, first.
+func Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
+	var report handlers.LibpodImagesRemoveReport
 	conn, err := bindings.GetClient(ctx)
 	if err != nil {
 		return nil, err
 	}
 	params := url.Values{}
-	if force != nil {
-		params.Set("force", strconv.FormatBool(*force))
+	params.Set("all", strconv.FormatBool(opts.All))
+	params.Set("force", strconv.FormatBool(opts.Force))
+	for _, i := range images {
+		params.Add("images", i)
 	}
-	response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID)
+
+	response, err := conn.DoRequest(nil, http.MethodGet, "/images/remove", params)
 	if err != nil {
 		return nil, err
 	}
-	return deletes, response.Process(&deletes)
+	if err := response.Process(&report); err != nil {
+		return nil, err
+	}
+	var rmError error
+	if report.Error != "" {
+		rmError = errors.New(report.Error)
+	}
+	return &report.ImageRemoveReport, rmError
 }
 
 // Export saves an image from local storage as a tarball or image archive.  The optional format
diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go
index fce8bbb8e3..e2f264139b 100644
--- a/pkg/bindings/system/system.go
+++ b/pkg/bindings/system/system.go
@@ -7,8 +7,8 @@ import (
 	"net/http"
 	"net/url"
 
-	"github.com/containers/libpod/pkg/api/handlers"
 	"github.com/containers/libpod/pkg/bindings"
+	"github.com/containers/libpod/pkg/domain/entities"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -16,7 +16,7 @@ import (
 // Events allows you to monitor libdpod related events like container creation and
 // removal.  The events are then passed to the eventChan provided. The optional cancelChan
 // can be used to cancel the read of events and close down the HTTP connection.
-func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
+func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
 	conn, err := bindings.GetClient(ctx)
 	if err != nil {
 		return err
@@ -48,7 +48,7 @@ func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan cha
 	}
 	dec := json.NewDecoder(response.Body)
 	for {
-		e := handlers.Event{}
+		e := entities.Event{}
 		if err := dec.Decode(&e); err != nil {
 			if err == io.EOF {
 				break
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index b730f87436..506d1c3170 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -54,7 +54,6 @@ type ContainerEngine interface {
 	PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error)
 	PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error)
 	PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
-	RestService(ctx context.Context, opts ServiceOptions) error
 	SetupRootless(ctx context.Context, cmd *cobra.Command) error
 	VarlinkService(ctx context.Context, opts ServiceOptions) error
 	VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index b6283b6ad9..84680ab1b5 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -9,7 +9,6 @@ import (
 type ImageEngine interface {
 	Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error)
 	Config(ctx context.Context) (*config.Config, error)
-	Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error)
 	Diff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
 	Exists(ctx context.Context, nameOrId string) (*BoolReport, error)
 	History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
@@ -20,6 +19,7 @@ type ImageEngine interface {
 	Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
 	Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
 	Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
+	Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, error)
 	Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
 	Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
 	Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go
new file mode 100644
index 0000000000..8861be1582
--- /dev/null
+++ b/pkg/domain/entities/events.go
@@ -0,0 +1,61 @@
+package entities
+
+import (
+	"strconv"
+	"time"
+
+	libpodEvents "github.com/containers/libpod/libpod/events"
+	dockerEvents "github.com/docker/docker/api/types/events"
+)
+
+// Event combines various event-related data such as time, event type, status
+// and more.
+type Event struct {
+	// TODO: it would be nice to have full control over the types at some
+	// point and fork such Docker types.
+	dockerEvents.Message
+}
+
+// ConvertToLibpodEvent converts an entities event to a libpod one.
+func ConvertToLibpodEvent(e Event) *libpodEvents.Event {
+	exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"])
+	if err != nil {
+		return nil
+	}
+	status, err := libpodEvents.StringToStatus(e.Action)
+	if err != nil {
+		return nil
+	}
+	t, err := libpodEvents.StringToType(e.Type)
+	if err != nil {
+		return nil
+	}
+	return &libpodEvents.Event{
+		ContainerExitCode: exitCode,
+		ID:                e.Actor.ID,
+		Image:             e.Actor.Attributes["image"],
+		Name:              e.Actor.Attributes["name"],
+		Status:            status,
+		Time:              time.Unix(e.Time, e.TimeNano),
+		Type:              t,
+	}
+}
+
+// ConvertToEntitiesEvent converts a libpod event to an entities one.
+func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
+	return &Event{dockerEvents.Message{
+		Type:   e.Type.String(),
+		Action: e.Status.String(),
+		Actor: dockerEvents.Actor{
+			ID: e.ID,
+			Attributes: map[string]string{
+				"image":             e.Image,
+				"name":              e.Name,
+				"containerExitCode": strconv.Itoa(e.ContainerExitCode),
+			},
+		},
+		Scope:    "local",
+		Time:     e.Time.Unix(),
+		TimeNano: e.Time.UnixNano(),
+	}}
+}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 56c4c0ac54..773cd90b44 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -82,19 +82,24 @@ func (i *ImageSummary) IsDangling() bool {
 	return i.Dangling
 }
 
-type ImageDeleteOptions struct {
-	All   bool
+// ImageRemoveOptions can be used to alter image removal.
+type ImageRemoveOptions struct {
+	// All will remove all images.
+	All bool
+	// Foce will force image removal including containers using the images.
 	Force bool
 }
 
-// ImageDeleteResponse is the response for removing one or more image(s) from storage
-// and containers what was untagged vs actually removed
-type ImageDeleteReport struct {
-	Untagged      []string `json:",omitempty"`
-	Deleted       []string `json:",omitempty"`
-	Errors        []error
-	ImageNotFound error
-	ImageInUse    error
+// ImageRemoveResponse is the response for removing one or more image(s) from storage
+// and containers what was untagged vs actually removed.
+type ImageRemoveReport struct {
+	// Deleted images.
+	Deleted []string `json:",omitempty"`
+	// Untagged images. Can be longer than Deleted.
+	Untagged []string `json:",omitempty"`
+	// ExitCode describes the exit codes as described in the `podman rmi`
+	// man page.
+	ExitCode int
 }
 
 type ImageHistoryOptions struct{}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 50003dbe23..e71ceb536f 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package abi
 
 import (
diff --git a/pkg/domain/infra/abi/events.go b/pkg/domain/infra/abi/events.go
index 9540a5b969..20773cdcef 100644
--- a/pkg/domain/infra/abi/events.go
+++ b/pkg/domain/infra/abi/events.go
@@ -1,5 +1,3 @@
-//+build ABISupport
-
 package abi
 
 import (
diff --git a/pkg/domain/infra/abi/healthcheck.go b/pkg/domain/infra/abi/healthcheck.go
index 6994832434..351bf4f7ed 100644
--- a/pkg/domain/infra/abi/healthcheck.go
+++ b/pkg/domain/infra/abi/healthcheck.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package abi
 
 import (
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 4353e0798d..32f7d75e58 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package abi
 
 import (
@@ -23,6 +21,7 @@ import (
 	domainUtils "github.com/containers/libpod/pkg/domain/utils"
 	"github.com/containers/libpod/pkg/util"
 	"github.com/containers/storage"
+	"github.com/hashicorp/go-multierror"
 	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
@@ -36,76 +35,6 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
 	return &entities.BoolReport{Value: err == nil}, nil
 }
 
-func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
-	report := entities.ImageDeleteReport{}
-
-	if opts.All {
-		var previousTargets []*libpodImage.Image
-	repeatRun:
-		targets, err := ir.Libpod.ImageRuntime().GetRWImages()
-		if err != nil {
-			return &report, errors.Wrapf(err, "unable to query local images")
-		}
-		if len(targets) == 0 {
-			return &report, nil
-		}
-		if len(targets) > 0 && len(targets) == len(previousTargets) {
-			return &report, errors.New("unable to delete all images; re-run the rmi command again.")
-		}
-		previousTargets = targets
-
-		for _, img := range targets {
-			isParent, err := img.IsParent(ctx)
-			if err != nil {
-				return &report, err
-			}
-			if isParent {
-				continue
-			}
-			err = ir.deleteImage(ctx, img, opts, report)
-			report.Errors = append(report.Errors, err)
-		}
-		if len(previousTargets) != 1 {
-			goto repeatRun
-		}
-		return &report, nil
-	}
-
-	for _, id := range nameOrId {
-		image, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
-		if err != nil {
-			return nil, err
-		}
-
-		err = ir.deleteImage(ctx, image, opts, report)
-		if err != nil {
-			return &report, err
-		}
-	}
-	return &report, nil
-}
-
-func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error {
-	results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
-	switch errors.Cause(err) {
-	case nil:
-		break
-	case storage.ErrImageUsedByContainer:
-		report.ImageInUse = errors.New(
-			fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
-		return nil
-	case libpodImage.ErrNoSuchImage:
-		report.ImageNotFound = err
-		return nil
-	default:
-		return err
-	}
-
-	report.Deleted = append(report.Deleted, results.Deleted)
-	report.Untagged = append(report.Untagged, results.Untagged...)
-	return nil
-}
-
 func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
 	results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter)
 	if err != nil {
@@ -488,3 +417,135 @@ func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.
 	}
 	return &entities.ImageTreeReport{Tree: results}, nil
 }
+
+// Remove removes one or more images from local storage.
+func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) {
+	var (
+		// noSuchImageErrors indicates that at least one image was not found.
+		noSuchImageErrors bool
+		// inUseErrors indicates that at least one image is being used by a
+		// container.
+		inUseErrors bool
+		// otherErrors indicates that at least one error other than the two
+		// above occured.
+		otherErrors bool
+		// deleteError is a multierror to conveniently collect errors during
+		// removal. We really want to delete as many images as possible and not
+		// error out immediately.
+		deleteError *multierror.Error
+	)
+
+	report = &entities.ImageRemoveReport{}
+
+	// Set the removalCode and the error after all work is done.
+	defer func() {
+		switch {
+		// 2
+		case inUseErrors:
+			// One of the specified images has child images or is
+			// being used by a container.
+			report.ExitCode = 2
+		// 1
+		case noSuchImageErrors && !(otherErrors || inUseErrors):
+			// One of the specified images did not exist, and no other
+			// failures.
+			report.ExitCode = 1
+		// 0
+		default:
+			// Nothing to do.
+		}
+		if deleteError != nil {
+			// go-multierror has a trailing new line which we need to remove to normalize the string.
+			finalError = deleteError.ErrorOrNil()
+			finalError = errors.New(strings.TrimSpace(finalError.Error()))
+		}
+	}()
+
+	// deleteImage is an anonymous function to conveniently delete an image
+	// withouth having to pass all local data around.
+	deleteImage := func(img *image.Image) error {
+		results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
+		switch errors.Cause(err) {
+		case nil:
+			break
+		case storage.ErrImageUsedByContainer:
+			inUseErrors = true // Important for exit codes in Podman.
+			return errors.New(
+				fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
+		default:
+			otherErrors = true // Important for exit codes in Podman.
+			return err
+		}
+
+		report.Deleted = append(report.Deleted, results.Deleted)
+		report.Untagged = append(report.Untagged, results.Untagged...)
+		return nil
+	}
+
+	// Delete all images from the local storage.
+	if opts.All {
+		previousImages := 0
+		// Remove all images one-by-one.
+		for {
+			storageImages, err := ir.Libpod.ImageRuntime().GetRWImages()
+			if err != nil {
+				deleteError = multierror.Append(deleteError,
+					errors.Wrapf(err, "unable to query local images"))
+				otherErrors = true // Important for exit codes in Podman.
+				return
+			}
+			// No images (left) to remove, so we're done.
+			if len(storageImages) == 0 {
+				return
+			}
+			// Prevent infinity loops by making a delete-progress check.
+			if previousImages == len(storageImages) {
+				otherErrors = true // Important for exit codes in Podman.
+				deleteError = multierror.Append(deleteError,
+					errors.New("unable to delete all images, check errors and re-run image removal if needed"))
+				break
+			}
+			previousImages = len(storageImages)
+			// Delete all "leaves" (i.e., images without child images).
+			for _, img := range storageImages {
+				isParent, err := img.IsParent(ctx)
+				if err != nil {
+					otherErrors = true // Important for exit codes in Podman.
+					deleteError = multierror.Append(deleteError, err)
+				}
+				// Skip parent images.
+				if isParent {
+					continue
+				}
+				if err := deleteImage(img); err != nil {
+					deleteError = multierror.Append(deleteError, err)
+				}
+			}
+		}
+
+		return
+	}
+
+	// Delete only the specified images.
+	for _, id := range images {
+		img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
+		switch errors.Cause(err) {
+		case nil:
+			break
+		case image.ErrNoSuchImage:
+			noSuchImageErrors = true // Important for exit codes in Podman.
+			fallthrough
+		default:
+			deleteError = multierror.Append(deleteError, err)
+			continue
+		}
+
+		err = deleteImage(img)
+		if err != nil {
+			otherErrors = true // Important for exit codes in Podman.
+			deleteError = multierror.Append(deleteError, err)
+		}
+	}
+
+	return
+}
diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go
index 68b961cb64..9add915eaa 100644
--- a/pkg/domain/infra/abi/images_list.go
+++ b/pkg/domain/infra/abi/images_list.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package abi
 
 import (
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 6b6e13e240..c4ae9efbf8 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package abi
 
 import (
diff --git a/pkg/domain/infra/abi/runtime.go b/pkg/domain/infra/abi/runtime.go
index b53fb6d3a1..7394cadfc9 100644
--- a/pkg/domain/infra/abi/runtime.go
+++ b/pkg/domain/infra/abi/runtime.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package abi
 
 import (
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index 078f5404dd..e5c109ee66 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -1,20 +1,15 @@
-// +build ABISupport
-
 package abi
 
 import (
 	"context"
 	"fmt"
 	"io/ioutil"
-	"net"
 	"os"
 	"strconv"
-	"strings"
 	"syscall"
 
 	"github.com/containers/common/pkg/config"
 	"github.com/containers/libpod/libpod/define"
-	api "github.com/containers/libpod/pkg/api/server"
 	"github.com/containers/libpod/pkg/cgroups"
 	"github.com/containers/libpod/pkg/domain/entities"
 	"github.com/containers/libpod/pkg/rootless"
@@ -33,42 +28,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
 	return ic.Libpod.Info()
 }
 
-func (ic *ContainerEngine) RestService(_ context.Context, opts entities.ServiceOptions) error {
-	var (
-		listener *net.Listener
-		err      error
-	)
-
-	if opts.URI != "" {
-		fields := strings.Split(opts.URI, ":")
-		if len(fields) == 1 {
-			return errors.Errorf("%s is an invalid socket destination", opts.URI)
-		}
-		address := strings.Join(fields[1:], ":")
-		l, err := net.Listen(fields[0], address)
-		if err != nil {
-			return errors.Wrapf(err, "unable to create socket %s", opts.URI)
-		}
-		listener = &l
-	}
-
-	server, err := api.NewServerWithSettings(ic.Libpod, opts.Timeout, listener)
-	if err != nil {
-		return err
-	}
-	defer func() {
-		if err := server.Shutdown(); err != nil {
-			logrus.Warnf("Error when stopping API service: %s", err)
-		}
-	}()
-
-	err = server.Serve()
-	if listener != nil {
-		_ = (*listener).Close()
-	}
-	return err
-}
-
 func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error {
 	var varlinkInterfaces = []*iopodman.VarlinkInterface{
 		iopodmanAPI.New(opts.Command, ic.Libpod),
diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go
index d7f5853d8b..b422e549ef 100644
--- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go
+++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package terminal
 
 import (
diff --git a/pkg/domain/infra/abi/terminal/terminal.go b/pkg/domain/infra/abi/terminal/terminal.go
index f187bdd6b7..0fc3af5119 100644
--- a/pkg/domain/infra/abi/terminal/terminal.go
+++ b/pkg/domain/infra/abi/terminal/terminal.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package terminal
 
 import (
diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go
index 664205df13..15701342f2 100644
--- a/pkg/domain/infra/abi/terminal/terminal_linux.go
+++ b/pkg/domain/infra/abi/terminal/terminal_linux.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
 package terminal
 
 import (
diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go
index 46d88341a9..93da3aeb4b 100644
--- a/pkg/domain/infra/tunnel/events.go
+++ b/pkg/domain/infra/tunnel/events.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"strings"
 
-	"github.com/containers/libpod/pkg/api/handlers"
 	"github.com/containers/libpod/pkg/bindings/system"
 	"github.com/containers/libpod/pkg/domain/entities"
 	"github.com/pkg/errors"
@@ -21,10 +20,10 @@ func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptio
 			filters[split[0]] = append(filters[split[0]], strings.Join(split[1:], "="))
 		}
 	}
-	binChan := make(chan handlers.Event)
+	binChan := make(chan entities.Event)
 	go func() {
 		for e := range binChan {
-			opts.EventChan <- e.ToLibpodEvent()
+			opts.EventChan <- entities.ConvertToLibpodEvent(e)
 		}
 	}()
 	return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters)
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 2decd605d1..8228429369 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -19,25 +19,8 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
 	return &entities.BoolReport{Value: found}, err
 }
 
-func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
-	report := entities.ImageDeleteReport{}
-
-	for _, id := range nameOrId {
-		results, err := images.Remove(ir.ClientCxt, id, &opts.Force)
-		if err != nil {
-			return nil, err
-		}
-		for _, e := range results {
-			if a, ok := e["Deleted"]; ok {
-				report.Deleted = append(report.Deleted, a)
-			}
-
-			if a, ok := e["Untagged"]; ok {
-				report.Untagged = append(report.Untagged, a)
-			}
-		}
-	}
-	return &report, nil
+func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
+	return images.Remove(ir.ClientCxt, imagesArg, opts)
 }
 
 func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) {
diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go
index f373525c5b..97bf885e77 100644
--- a/pkg/domain/infra/tunnel/system.go
+++ b/pkg/domain/infra/tunnel/system.go
@@ -14,10 +14,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
 	return system.Info(ic.ClientCxt)
 }
 
-func (ic *ContainerEngine) RestService(_ context.Context, _ entities.ServiceOptions) error {
-	panic(errors.New("rest service is not supported when tunneling"))
-}
-
 func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceOptions) error {
 	panic(errors.New("varlink service is not supported when tunneling"))
 }