A good rule-of-thumb is that anything a user would expect to keep after changing themes belongs in a plugin. This includes custom post types, taxonomies, metaboxes, and shortcodes.
For specific functionality that’s reusable and/or the client might want to selectively disable, it’s best to build as its own plugin. Many times I’ll turn these into public plugins.
For everything else that’s project-specific but should be theme-independent, I put in a plugin called Core Functionality. That’s the base plugin I now use. Of course you’ll want to delete everything that’s not relevant – I have the code for post types, taxonomies, and metaboxes in there because they are used most often.
My base child theme used to have all these in the theme. Over the past few months I’ve been moving them to a core functionality plugin on each project, but it’s about time I finally update the base child theme and pull the functionality out into its own plugin.
For added safety, I use a “must use” plugin rather than a standard plugin. Must Use plugins (mu-plugins), are plugins that cannot be disabled. I like to put my core functionality in these so that a client can’t accidentally disable it and break their site. Must Use plugins go in /wp-content/mu-plugins (you’ll need to create that directory if it doesn’t already exist).