diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go
index 69afdc02c6..a3db264c2f 100644
--- a/cmd/podman/machine/init.go
+++ b/cmd/podman/machine/init.go
@@ -62,6 +62,10 @@ func init() {
 	)
 	_ = initCmd.RegisterFlagCompletionFunc(cpusFlagName, completion.AutocompleteNone)
 
+	runPlaybookFlagName := "playbook"
+	flags.StringVar(&initOpts.PlaybookPath, runPlaybookFlagName, "", "Run an Ansible playbook after first boot")
+	_ = initCmd.RegisterFlagCompletionFunc(runPlaybookFlagName, completion.AutocompleteDefault)
+
 	diskSizeFlagName := "disk-size"
 	flags.Uint64Var(
 		&initOpts.DiskSize,
diff --git a/docs/source/markdown/podman-machine-init.1.md.in b/docs/source/markdown/podman-machine-init.1.md.in
index af603e0d4e..775362a9e9 100644
--- a/docs/source/markdown/podman-machine-init.1.md.in
+++ b/docs/source/markdown/podman-machine-init.1.md.in
@@ -80,6 +80,8 @@ is copied into the user's CONF_DIR and renamed.  Additionally, no SSH keys are g
 Fully qualified registry, path, or URL to a VM image.
 Registry target must be in the form of `docker://registry/repo/image:version`.
 
+Note: Only images provided by podman will be supported.
+
 #### **--memory**, **-m**=*number*
 
 Memory (in MiB). Note: 1024MiB = 1GiB.
@@ -88,6 +90,13 @@ Memory (in MiB). Note: 1024MiB = 1GiB.
 
 Start the virtual machine immediately after it has been initialized.
 
+#### **--playbook**
+
+Add the provided Ansible playbook to the machine and execute it after the first boot.
+
+Note: The playbook will be executed with the same privileges given to the user in the virtual machine. The playbook provided cannot include other files from the host system, as they will not be copied.
+Use of the `--playbook` flag will require the image to include Ansible. The default image provided will have Ansible included.
+
 #### **--rootful**
 
 Whether this machine prefers rootful (`true`) or rootless (`false`)
diff --git a/pkg/machine/define/initopts.go b/pkg/machine/define/initopts.go
index 4ddf87ca19..b1ccaaa6b7 100644
--- a/pkg/machine/define/initopts.go
+++ b/pkg/machine/define/initopts.go
@@ -3,6 +3,7 @@ package define
 import "net/url"
 
 type InitOptions struct {
+	PlaybookPath       string
 	CPUS               uint64
 	DiskSize           uint64
 	IgnitionPath       string
diff --git a/pkg/machine/e2e/config_init_test.go b/pkg/machine/e2e/config_init_test.go
index 0423010477..031f4395f7 100644
--- a/pkg/machine/e2e/config_init_test.go
+++ b/pkg/machine/e2e/config_init_test.go
@@ -11,19 +11,21 @@ import (
 
 type initMachine struct {
 	/*
-	      --cpus uint              Number of CPUs (default 1)
-	      --disk-size uint         Disk size in GiB (default 100)
-	      --ignition-path string   Path to ignition file
-	      --username string        Username of the remote user (default "core" for FCOS, "user" for Fedora)
-	      --image-path string      Path to bootable image (default "testing")
-	  -m, --memory uint            Memory in MiB (default 2048)
-	      --now                    Start machine now
-	      --rootful                Whether this machine should prefer rootful container execution
-	      --timezone string        Set timezone (default "local")
-	  -v, --volume stringArray     Volumes to mount, source:target
-	      --volume-driver string   Optional volume driver
+			      --cpus uint              Number of CPUs (default 1)
+			      --disk-size uint         Disk size in GiB (default 100)
+			      --ignition-path string   Path to ignition file
+			      --username string        Username of the remote user (default "core" for FCOS, "user" for Fedora)
+			      --image-path string      Path to bootable image (default "testing")
+			  -m, --memory uint            Memory in MiB (default 2048)
+			      --now                    Start machine now
+			      --rootful                Whether this machine should prefer rootful container execution
+		          --playbook string        Run an ansible playbook after first boot
+			      --timezone string        Set timezone (default "local")
+			  -v, --volume stringArray     Volumes to mount, source:target
+			      --volume-driver string   Optional volume driver
 
 	*/
+	playbook           string
 	cpus               *uint
 	diskSize           *uint
 	ignitionPath       string
@@ -73,6 +75,9 @@ func (i *initMachine) buildCmd(m *machineTestBuilder) []string {
 	if i.rootful {
 		cmd = append(cmd, "--rootful")
 	}
+	if l := len(i.playbook); l > 0 {
+		cmd = append(cmd, "--playbook", i.playbook)
+	}
 	if i.userModeNetworking {
 		cmd = append(cmd, "--user-mode-networking")
 	}
@@ -152,6 +157,11 @@ func (i *initMachine) withRootful(r bool) *initMachine {
 	return i
 }
 
+func (i *initMachine) withRunPlaybook(p string) *initMachine {
+	i.playbook = p
+	return i
+}
+
 func (i *initMachine) withUserModeNetworking(r bool) *initMachine { //nolint:unused
 	i.userModeNetworking = r
 	return i
diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go
index bd5fe683c5..8e3bc72b92 100644
--- a/pkg/machine/e2e/init_test.go
+++ b/pkg/machine/e2e/init_test.go
@@ -98,6 +98,73 @@ var _ = Describe("podman machine init", func() {
 		}
 	})
 
+	It("run playbook", func() {
+		str := randomString()
+
+		// ansible playbook file to create a text file containing a random string
+		playbookContents := fmt.Sprintf(`- name: Simple podman machine example
+  hosts: localhost
+  tasks:
+    - name: create a file
+      ansible.builtin.copy:
+        dest: ~/foobar.txt
+        content: "%s\n"`, str)
+
+		playbookPath := filepath.Join(GinkgoT().TempDir(), "playbook.yaml")
+
+		// create the playbook file
+		playbookFile, err := os.Create(playbookPath)
+		Expect(err).ToNot(HaveOccurred())
+		defer playbookFile.Close()
+
+		// write the desired contents into the file
+		_, err = playbookFile.WriteString(playbookContents)
+		Expect(err).To(Not(HaveOccurred()))
+
+		name := randomString()
+		i := new(initMachine)
+		session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath).withRunPlaybook(playbookPath).withNow()).run()
+		Expect(err).ToNot(HaveOccurred())
+		Expect(session).To(Exit(0))
+
+		// ensure the contents of the playbook file didn't change when getting copied
+		ssh := new(sshMachine)
+		sshSession, err := mb.setName(name).setCmd(ssh.withSSHCommand([]string{"cat", "playbook.yaml"})).run()
+		Expect(err).ToNot(HaveOccurred())
+		Expect(sshSession).To(Exit(0))
+		Expect(sshSession.outputToStringSlice()).To(Equal(strings.Split(playbookContents, "\n")))
+
+		// wait until the playbook.service is done before checking to make sure the playbook was a success
+		playbookFinished := false
+		for range 900 {
+			sshSession, err = mb.setName(name).setCmd(ssh.withSSHCommand([]string{"systemctl", "is-active", "playbook.service"})).run()
+			Expect(err).ToNot(HaveOccurred())
+
+			if sshSession.outputToString() == "inactive" {
+				playbookFinished = true
+				break
+			}
+
+			time.Sleep(10 * time.Millisecond)
+		}
+
+		if !playbookFinished {
+			Fail("playbook.service did not finish")
+		}
+
+		// output the contents of the file generated by the playbook
+		guestUser := "core"
+		if isWSL() {
+			guestUser = "user"
+		}
+		sshSession, err = mb.setName(name).setCmd(ssh.withSSHCommand([]string{"cat", fmt.Sprintf("/home/%s/foobar.txt", guestUser)})).run()
+		Expect(err).ToNot(HaveOccurred())
+		Expect(sshSession).To(Exit(0))
+
+		// check its the same as the random number or string that we generated
+		Expect(sshSession.outputToString()).To(Equal(str))
+	})
+
 	It("simple init with start", func() {
 		i := initMachine{}
 		session, err := mb.setCmd(i.withImage(mb.imagePath)).run()
diff --git a/pkg/machine/ignition/ignition.go b/pkg/machine/ignition/ignition.go
index 62bf7a872f..3995ef714c 100644
--- a/pkg/machine/ignition/ignition.go
+++ b/pkg/machine/ignition/ignition.go
@@ -685,6 +685,51 @@ done
 `
 }
 
+func (i *IgnitionBuilder) AddPlaybook(contents string, destPath string, username string) error {
+	// create the ignition file object
+	f := File{
+		Node: Node{
+			Group: GetNodeGrp(username),
+			Path:  destPath,
+			User:  GetNodeUsr(username),
+		},
+		FileEmbedded1: FileEmbedded1{
+			Append: nil,
+			Contents: Resource{
+				Source: EncodeDataURLPtr(contents),
+			},
+			Mode: IntToPtr(0744),
+		},
+	}
+
+	// call ignitionBuilder.WithFile
+	// add the config file to the ignition object
+	i.WithFile(f)
+
+	unit := parser.NewUnitFile()
+	unit.Add("Unit", "After", "ready.service")
+	unit.Add("Unit", "ConditionFirstBoot", "yes")
+	unit.Add("Service", "Type", "oneshot")
+	unit.Add("Service", "User", username)
+	unit.Add("Service", "Group", username)
+	unit.Add("Service", "ExecStart", fmt.Sprintf("ansible-playbook %s", destPath))
+	unit.Add("Install", "WantedBy", "default.target")
+	unitContents, err := unit.ToString()
+	if err != nil {
+		return err
+	}
+
+	// create a systemd service
+	playbookUnit := Unit{
+		Enabled:  BoolToPtr(true),
+		Name:     "playbook.service",
+		Contents: &unitContents,
+	}
+	i.WithUnit(playbookUnit)
+
+	return nil
+}
+
 func GetNetRecoveryUnitFile() *parser.UnitFile {
 	recoveryUnit := parser.NewUnitFile()
 	recoveryUnit.Add("Unit", "Description", "Verifies health of network and recovers if necessary")
diff --git a/pkg/machine/shim/host.go b/pkg/machine/shim/host.go
index 1315c9adb9..82e70be8fa 100644
--- a/pkg/machine/shim/host.go
+++ b/pkg/machine/shim/host.go
@@ -4,6 +4,7 @@ import (
 	"bufio"
 	"errors"
 	"fmt"
+	"io"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -207,6 +208,32 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
 		}
 	}
 
+	if len(opts.PlaybookPath) > 0 {
+		f, err := os.Open(opts.PlaybookPath)
+		if err != nil {
+			return err
+		}
+		s, err := io.ReadAll(f)
+		if err != nil {
+			return fmt.Errorf("read playbook: %w", err)
+		}
+
+		playbookDest := fmt.Sprintf("/home/%s/%s", userName, "playbook.yaml")
+
+		if mp.VMType() != machineDefine.WSLVirt {
+			err = ignBuilder.AddPlaybook(string(s), playbookDest, userName)
+			if err != nil {
+				return err
+			}
+		}
+
+		mc.Ansible = &vmconfigs.AnsibleConfig{
+			PlaybookPath: playbookDest,
+			Contents:     string(s),
+			User:         userName,
+		}
+	}
+
 	readyIgnOpts, err := mp.PrepareIgnition(mc, &ignBuilder)
 	if err != nil {
 		return err
@@ -543,6 +570,16 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDe
 		}
 	}
 
+	isFirstBoot, err := mc.IsFirstBoot()
+	if err != nil {
+		logrus.Error(err)
+	}
+	if mp.VMType() == machineDefine.WSLVirt && mc.Ansible != nil && isFirstBoot {
+		if err := machine.CommonSSHSilent(mc.Ansible.User, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, []string{"ansible-playbook", mc.Ansible.PlaybookPath}); err != nil {
+			logrus.Error(err)
+		}
+	}
+
 	// Provider is responsible for waiting
 	if mp.UseProviderNetworkSetup() {
 		return nil
diff --git a/pkg/machine/vmconfigs/config.go b/pkg/machine/vmconfigs/config.go
index 417d5b3fe1..a45fee6432 100644
--- a/pkg/machine/vmconfigs/config.go
+++ b/pkg/machine/vmconfigs/config.go
@@ -53,6 +53,8 @@ type MachineConfig struct {
 	Starting bool
 
 	Rosetta bool
+
+	Ansible *AnsibleConfig
 }
 
 type machineImage interface { //nolint:unused
@@ -148,3 +150,9 @@ type VMStats struct {
 	// LastUp contains the last recorded uptime
 	LastUp time.Time
 }
+
+type AnsibleConfig struct {
+	PlaybookPath string
+	Contents     string
+	User         string
+}
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index 38c1a91261..3355f6b2b5 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -148,7 +148,7 @@ func createKeys(mc *vmconfigs.MachineConfig, dist string) error {
 	return nil
 }
 
-func configureSystem(mc *vmconfigs.MachineConfig, dist string) error {
+func configureSystem(mc *vmconfigs.MachineConfig, dist string, ansibleConfig *vmconfigs.AnsibleConfig) error {
 	user := mc.SSH.RemoteUsername
 	if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, mc.SSH.Port, mc.SSH.Port)); err != nil {
 		return fmt.Errorf("could not configure SSH port for guest OS: %w", err)
@@ -167,6 +167,12 @@ func configureSystem(mc *vmconfigs.MachineConfig, dist string) error {
 		return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err)
 	}
 
+	if ansibleConfig != nil {
+		if err := wslPipe(ansibleConfig.Contents, dist, "sh", "-c", fmt.Sprintf("cat > %s", ansibleConfig.PlaybookPath)); err != nil {
+			return fmt.Errorf("could not generate playbook file for guest os: %w", err)
+		}
+	}
+
 	lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
 	if err := wslPipe(lingerService, dist, "sh", "-c", lingerCmd); err != nil {
 		return fmt.Errorf("could not generate linger service for guest OS: %w", err)
diff --git a/pkg/machine/wsl/stubber.go b/pkg/machine/wsl/stubber.go
index 073b0b04bf..75b12fad03 100644
--- a/pkg/machine/wsl/stubber.go
+++ b/pkg/machine/wsl/stubber.go
@@ -68,7 +68,7 @@ func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConf
 	}
 
 	fmt.Println("Configuring system...")
-	if err = configureSystem(mc, dist); err != nil {
+	if err = configureSystem(mc, dist, mc.Ansible); err != nil {
 		return err
 	}