diff --git a/orbit/pkg/packaging/macos_notarize.go b/orbit/pkg/packaging/macos_notarize.go
index 7fab97b320..223821324c 100644
--- a/orbit/pkg/packaging/macos_notarize.go
+++ b/orbit/pkg/packaging/macos_notarize.go
@@ -13,7 +13,7 @@ import (
"github.com/rs/zerolog/log"
)
-// Notarize will do the Notarization step. Note that the provided path must be a .zip or a .dmg.
+// Notarize will do the Notarization step. Note that the provided path must be a .zip, .dmg or .pkg.
func Notarize(path, bundleIdentifier string) error {
username, ok := os.LookupEnv("AC_USERNAME")
if !ok {
@@ -46,7 +46,7 @@ func Notarize(path, bundleIdentifier string) error {
return nil
}
-// Staple will do the "stapling" step of Notarization. Note that this only works on .app and .dmg
+// Staple will do the "stapling" step of Notarization. Note that this only works on .app, .pkg and .dmg
// (not .zip or plain binaries).
func Staple(path string) error {
if err := staple.Staple(context.Background(), &staple.Options{File: path}); err != nil {
diff --git a/proposals/.keep b/proposals/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/proposals/Fleet-Installers-4-Sandbox.md b/proposals/Fleet-Installers-4-Sandbox.md
new file mode 100644
index 0000000000..7043c05fe9
--- /dev/null
+++ b/proposals/Fleet-Installers-4-Sandbox.md
@@ -0,0 +1,50 @@
+# Fleet Sandbox & Pre-Packaged Fleet-osquery Installers
+
+## Goals
+
+1. Improve UX on Fleet Sandbox by offering pre-packaged Fleet-osquery installers.
+2. Add the "Pre-Packaged installers" feature to "Fleet Sandbox" as soon as possible (i.e. not block on having a fully functional "Fleet Packager" service).
+
+## Fleet Sandbox Assumptions
+
+- We will limit number of teams to T.
+- Sandbox has good root CA trusted certificates
+- Users won't be allowed to change enroll secrets.
+
+## Pre-Packaged Installers Plan
+
+We will need some changes to fleetctl, the pre-provisioner, the Fleet server and UI.
+
+### fleetctl
+
+Make all functionality in `fleetctl package` to run in linux. (This change will be needed for the Packager service anyways.)
+
+PS: Abstract in its own package so that it can be used by Packager service in a next iteration.
+
+### Pre-provisioner
+
+Following are the pre-provisioner steps to generate the pre-packaged installers:
+
+1. Generate T+1 random enroll secrets.
+
+2. Run `fleetctl package --type={pkg|rpm|deb|msi}` with T+1 enroll secrets (i.e. one for Global and one for each team).
+PS: There's some complexity in storing/handling credentials for macOS Signing and Notarization of the packages.
+
+3. The generated packages will be stored in a S3 bucket accessible by the Fleet server with the following object name format
+`$INSTALLERS_DIR/$ENROLL_SECRET/fleet-osquery.$TYPE`, e.g. `/fleet-installers/FzRCZWTlEY2kqzIwk1BE9fru5KuhrlYP/fleet-osquery.pkg`.
+We propose using S3 to support multiple Fleet instances serving the requests.
+
+4. Set comma-separated `FLEET_ENROLL_POOL` environment variable to Fleet server config (Fleet would use those secrets instead of randomly generating one).
+The Fleet server will only serve the installers with enroll secret listed in this variable (security check).
+
+### Fleet Server and UI
+
+- Fleet server new configuration and new functionality:
+ - `FLEET_MAX_TEAMS`: Maximum number of teams to allow in the deployment (default 0, disabled).
+ - `FLEET_DISABLE_ENROLL_CHANGE`: Disallow users from changing enroll secrets (default false).
+ - `FLEET_PACKAGES_S3_*`: S3 configuration for the retrieval of the pre-packaged installers (default empty).
+ - `FLEET_ENROLL_POOL`: comma-separated enrolls to use when needed (default empty), must have equals to FLEET_MAX_TEAMS+1 items (default empty).
+
+- Fleet will serve a new authenticated API (for Sandbox-only): `{GET|HEAD} /api/v1/fleet/download_installer/{enroll}/{type}`, e.g. `GET /api/v1/fleet/download_installer/FzRCZWTlEY2kqzIwk1BE9fru5KuhrlYP/rpm`.
+ - The UI can make a `HEAD` request to check if an installer exists, if so, then it can display a download button for it, (if not, "show the current UI"? TBD with UI team)
+ - The API looks for the installer corresponding to the Global/Team the user is looking at, and returns it for download.
diff --git a/proposals/Fleet-Installers.md b/proposals/Fleet-Installers.md
new file mode 100644
index 0000000000..f6604e36c4
--- /dev/null
+++ b/proposals/Fleet-Installers.md
@@ -0,0 +1,469 @@
+# Fleet Installers
+
+## Goal
+
+[#5757](https://github.com/fleetdm/fleet/issues/5757)
+
+```
+As a user, I want to be able to download a Fleet-osquery installer (aka Orbit) in the Fleet UI
+so that I can add hosts to Fleet without having to know how to successfully generate an
+installer with the `fleetctl package` command.
+
+Figma wireframes: https://www.figma.com/file/hdALBDsrti77QuDNSzLdkx/?node-id=6740%3A267448
+```
+
+## Command `fleetctl package`
+
+Currently users use the `fleetctl package` command to generate Fleet-osquery packages.
+
+The `fleetctl package` command has the following *required* configuration that is specific to a "Fleet Deployment":
+- `--fleet-url`: The URL that the hosts will use to connect to the Fleet server.
+- `--enroll-secret`: Global or team enroll secret to use when enrolling the host to Fleet.
+
+As the goal states, we would like to provide functionality in Fleet to automatically generate and download such packages from the UI.
+
+## How
+
+We can implement such functionality in two ways:
+
+- Option A. Fleet Server to generate such packages itself.
+- Option B. Separate "Packager" service.
+
+There's a lot of platform specific logic and tooling involved in packaging, and one of Fleet's primary goals is to keep deployment/infrastructure simple for On-Prem deployments.
+To that end, we believe the best option is Option B, implementing the functionality as a separate service.
+
+## Packager Service
+
+The "Fleet Packager" service will implement all the package generation logic. Think of it like offering the `fleetctl package` command functionality but as a REST service.
+
+```mermaid
+graph LR;
+ A[User-Agent/
Browser]-- Fleet API -->B[Fleet
UI/Server]
+ A-- Packager
API -->C
+ subgraph FleetDM Hosted Infrastructure
+ direction LR
+ subgraph
pkg.fleetctl.com
+ direction TB;
+ C[Packager
Service]
+ packages[(Generated
packages)]
+ end
+ direction TB;
+ D[tuf.fleetctl.com]
+ C-- Fetch
Targets -->D;
+ end
+ E[Apple's
Notary
Server]
+ C-- Notary
API ---->E
+```
+
+Configurations:
+- The `Fleet UI/Server` will allow configuring the "Fleet Packager" URL (default value being FleetDM Hosted Packager service, something like `pkg.fleetctl.com`).
+- The `Packager Service` will allow configuring an alternative TUF server URL and TUF roots via an environmental variable (default value being FleetDM Hosted TUF, `tuf.fleetctl.com`).
+
+Both of these configurations will allow users to deploy their own `Packager Service`.
+
+### Storage
+
+The generated packages should be stored on encrypted disk and will expire (will be deleted) after a configurable amount of time (default 30m).
+There are two reasons we want to expire generated packages soon:
+1. To not store user credentials for too long (URL and enroll secret).
+2. To free up space.
+
+We can use "Storage Optimized" instances (see https://aws.amazon.com/ec2/instance-types/).
+
+#### Notes
+
+- As a possible future optimization, we could use "Memory Optimized" instances and store packages in RAM instead of using hard disk.
+- S3 could also be used to store such packages, but disk storage is needed to generate the packages in the first place.
+So hard-disk will be a dependency anyways (and ideally we would like these packages with sensitive credentials to be stored in one location).
+- From Roberto: "sounds like the main bottleneck here will be transferring the data over the network to the user doing the request.".
+In other words, we should apply all optimizations on the network (like caching, reducing package size, etc.), that will be our main bottleneck
+(not CPU or hard-disk access).
+
+#### Back of the Envelope
+
+- `.pkg`s use ~70MB of storage.
+- `.msi`s use ~20MB of storage.
+- `.deb`s and `.rpm`s use ~75MB of storage.
+
+Assuming the worst case of ~75 MB for each package:
+If we have a ~30TB hard disk, it would allow storing ~400_000 packages simultaneously.
+
+### Network & Credentials
+
+The service will require network access to (URLs provided via config):
+
+- TUF server.
+- Apple's Notary Server (for generating `.pkg`).
+
+The packaging service will need the following credentials (provided via config):
+
+- Apple credentials:
+ - Codesign identity.
+ - Username and Password for notarization.
+- TUF server update roots (default will be the hardcoded one for FleetDM's hosted TUF server, tuf.fleetctl.com).
+
+### Packager REST API
+
+For the MVF (Minimum Viable Feature) we'll need three APIs: one for creation/submission, one for checking status and another one for the actual download of the package.
+All the APIs must be rate-limited to prevent abuse of the system.
+
+#### 1. Package creation
+
+`POST /create`
+
+This endpoint will perform the following operations:
+1. Check if a `package_id` already exists (and hasn't been expired) with the exact same arguments, if so, return HTTP 200 with the `package_id`.
+2. Generate a [random](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)) `package_id`.
+3. Dispatch the creation of a package with ID set to `package_id` and the given request parameters.
+4. Return HTTP 200 with the `package_id`.
+
+This endpoint, which is the entrypoint, should be rate-limited by IP.
+
+##### Request Fields
+
+| Name | Type | In | Description |
+| ----------------- | ------- | ---- | -------------------------------------------------------------------------------------- |
+| type | string | body | **Required.** One of the following values "pkg", "msi", "deb", "rpm" |
+| fleet_url | string | body | **Required.** The URL that the hosts will use to connect to the Fleet server |
+| enroll_secret | string | body | **Required.** Global or team enroll secret to use when enrolling the host to Fleet |
+| retry | boolean | body | Retry a failed package generation (default: false) |
+| fleet-certificate | string | body | Server certificate chain |
+| insecure | boolean | body | Disable TLS certificate verification (default: false) |
+| osqueryd-channel | string | body | Update channel of osqueryd to use (default: "stable") |
+| desktop-channel | string | body | Update channel of desktop to use (default: "stable") |
+| orbit-channel | string | body | Update channel of Orbit to use (default: "stable") |
+| disable-updates | boolean | body | Disable auto updates on the generated package (default: false) |
+| debug | boolean | body | Enable debug logging in orbit (default: false) |
+| fleet-desktop | boolean | body | Include the Fleet Desktop Application in the package (default: false) |
+| update-interval | string | body | Interval that Orbit will use to check for new updates (10s, 1h, etc.) (default: 15m0s) |
+| osquery-flagfile | string | body | Flagfile to package and provide to osquery (default: empty) |
+| service | boolean | body | Install with a persistence service (launchd, systemd, etc.) (default: true) |
+
+##### Error in Package Generation
+
+When the set of arguments correspond to a `package_id` that failed to generate, then:
+- If `retry` is `false` (default) it will return such `package_id`.
+- If `retry` is set to `true`, the service will dispatch a new package build and return a new `package_id`.
+
+##### Response Fields
+
+| Name | Type | In | Description |
+| ----------------- | ------- | ---- | -------------------------------- |
+| package_id | string | body | ID of the package being created. |
+
+#### 2. Package Status Check
+
+`GET /status`
+
+This endpoint allows checking the status of a package being created.
+Clients can poll for the status of a package using this API.
+
+This endpoint should be rate-limited by `package_id`.
+
+##### Request Fields
+
+| Name | Type | In | Description |
+| ----------------- | ------- | ----- | ------------------------------------------------------------------------ |
+| package_id | string | query | **Required.** ID of the package created with `POST /create` |
+
+##### Response Fields
+
+| Name | Type | In | Description |
+| ----------------- | ------- | ---- | ---------------------------------------------------------------------------- |
+| status | string | body | One of the following values "success", "fail", "in-progress", "expired" |
+| download_url | string | body | Set to the download URL if status field is "success" |
+| stage | string | body | Set to a "stage" string if status field is "in-progress" (e.g. "notarizing") |
+| logs | string | body | Contains logs if status is "failed" |
+
+#### 3. Package Download
+
+`GET /download/{package_id}`
+
+This is the API to download the generated package.
+
+This endpoint should be rate-limited by `package_id`.
+
+## Sequence Diagram
+
+Following is the sequence diagram for the happy-path.
+
+```mermaid
+sequenceDiagram
+ User-Agent/Browser->>Fleet: GET /api/v1/fleet/config
+ Fleet-->>User-Agent/Browser: packager_url
+ User-Agent/Browser->>Packager: POST /create
+ Packager-->>User-Agent/Browser: package_id
+ Packager-->>TUF Server: Fetch targets
+ loop
+ User-Agent/Browser->>Packager: GET /status
+ Packager-->>User-Agent/Browser: status == "in-progress"
+ Note over User-Agent/Browser: Retry every ~10 seconds,
until status is "success"
+ TUF Server->>Packager: Targets
+ Note over Packager: Build package
+ Note over Packager: Sign package
+ rect rgb(128, 128, 128)
+ Note right of Packager: (When generating macOS pkgs)
+ Packager-->>Apple's Notary Server: New SubmissionRequest (upload)
+ Apple's Notary Server->>Packager: NewSubmissionResponse
+ Note over Packager: Upload to S3
(see Apple docs)
+ loop Every ~30 seconds, until status is "Accepted"
+ Packager->>Apple's Notary Server: Get submission status
+ Apple's Notary Server-->>Packager: status
+ end
+ end
+ end
+ User-Agent/Browser->>Packager: GET /download/{package_id}
+ Packager-->>User-Agent/Browser: Package File
+```
+
+## Package Generation Time
+
+Some back of the envelope calculations for the time the user clicks Download to the time the installer is downloaded fully.
+
+### macOS
+
+Test running `time fleetctl package --type=pkg --fleet-url=... --enroll-secret=... --fleet-desktop` (from Argentina).
+
+1. Download packages from TUF and generate raw `.pkg` (16 seconds).
+2. Signing (assuming this is negligible).
+3. Notarization (~3 minutes, from Zach's tests mentioned below).
+4. Download from Packager service (~15 seconds to download a 100 MB file).
+
+Total: ~4 minutes
+
+### Windows
+
+Test running `time fleetctl package --type=msi --fleet-url=... --enroll-secret=... --fleet-desktop` (from Argentina).
+
+1. Download packages from TUF and generate raw `.msi` (~18 seconds).
+2. Download from Packager service (~15 seconds to download a 100 MB file).
+
+Total: ~30 seconds.
+
+### Linux
+
+Test running `time fleetctl package --type={deb|rpm} --fleet-url=... --enroll-secret=... --fleet-desktop` (from Argentina).
+
+1. Download packages from TUF and generate raw `.{deb|rpm}` (~30 seconds).
+2. Download from Packager service (~30 seconds to download a 200 MB file).
+
+Total: ~1 minute.
+
+### Possible Download Time Optimization
+
+- The Packager service could cache the TUF targets for a couple of minutes (given that they usually change ~ every three weeks).
+This would reduce 15s-30s the time it takes to generate the packages.
+
+## Platform Specifics
+
+### macOS
+
+There are two operations **required** to have a proper macOS installer package (`.pkg`):
+
+1. Code signing: Used by Gatekeeper to verify the author of the package (identified by Developer ID)
+2. Notarization: "Gives users more confidence that the Developer ID-signed software you distribute has been checked by Apple for malicious components."
+
+The packages are composed by three TUF targets: osquery, Orbit and Fleet Desktop.
+
+All the TUF targets served by FleetDM's TUF server are signed and notarized:
+
+- osquery: signed and notarized .app (by Osquery)
+- Orbit: signed and notarized executable (by Fleet DM)
+- fleet-desktop: signed and notarized .app (by Fleet DM)
+
+Even if all targets are signed and notarized we must still sign and notarize the `.pkg` installer as a whole, see [#122045](https://developer.apple.com/forums/thread/122045).
+
+#### Notarization
+
+The Notarization process can be summarized to the following steps:
+
+1. Submit/Upload the **signed** package to the Notary Server.
+2. Notary server performs automated security checks.
+3. Notary generates a ticket, publishes ticket online (so that Gatekeeper can find it) and returns the ticket.
+4. Ticket can be stapled to your software to let Gatekeeper know it has been notarized.
+
+##### Notarization Limitations
+
+- Notarization completes for most software within 5 minutes, and for 98 percent of software within 15 minutes. Thus our workflow must support users leaving the "Add hosts" page and returning later.
+- To help avoid long response times they suggest: to "limit notarizations to 75 per day." So it doesn't look like a hard limit, just something that would help reduce upload response times.
+Source: https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
+Though Zach has ran the following test with no issues:
+> I was able to Notarize ~200 packages in under 12 hours yesterday with no apparent limiting.
+> Note this was using the older Notarization API (slower, about 3 mins per notarization).
+
+#### Future Optimizations for Notarization
+
+This is the current structure of a `.pkg` generated with `fleetctl package` (unsigned and unnotarized):
+```sh
+pkg_expanded
+├── Distribution
+└── base
+ ├── Bom
+ ├── Library
+ │ └── LaunchDaemons
+ │ └── com.fleetdm.orbit.plist
+ ├── PackageInfo
+ ├── Payload
+ ├── Scripts
+ │ └── postinstall
+ └── opt
+ └── orbit
+ ├── bin
+ │ ├── desktop
+ │ │ └── macos
+ │ │ └── stable
+ │ │ ├── Fleet Desktop.app
+ │ │ │ └── Contents
+ │ │ │ ├── CodeResources
+ │ │ │ ├── Info.plist
+ │ │ │ ├── MacOS
+ │ │ │ │ └── fleet-desktop
+ │ │ │ └── _CodeSignature
+ │ │ │ └── CodeResources
+ │ │ └── desktop.app.tar.gz
+ │ ├── orbit
+ │ │ └── macos
+ │ │ └── stable
+ │ │ └── orbit
+ │ └── osqueryd
+ │ └── macos-app
+ │ └── stable
+ │ ├── osquery.app
+ │ │ └── Contents
+ │ │ ├── Info.plist
+ │ │ ├── MacOS
+ │ │ │ └── osqueryd
+ │ │ ├── PkgInfo
+ │ │ ├── Resources
+ │ │ │ └── osqueryctl
+ │ │ ├── _CodeSignature
+ │ │ │ └── CodeResources
+ │ │ └── embedded.provisionprofile
+ │ └── osqueryd.app.tar.gz
+ ├── certs.pem
+ ├── osquery.flags
+ ├── secret.txt
+ ├── staging
+ └── tuf-metadata.json
+```
+
+##### Remove Unnecessary Files
+
+The `osqueryd.app.tar.gz` and `desktop.app.tar.gz` files are used by Orbit to compare with remote targets when checking for updates.
+A future optimization could replace those `.app.tar.gz` files with a txt file with the hash of such file
+(would reduce ~30MB of uncompressed size reduction for the `.pkg`).
+
+##### Notarized Package without Configs
+
+The only files that really change when users generate a pkg are:
+- Library/LaunchDaemons/com.fleetdm.orbit.plist (contains the configuration, like URLs, update channels, etc.)
+- opt/orbit/secret.txt (enroll secret)
+
+Another idea to consider could be packaging and notarizing code and scripts, but leave specific configs out of the `.pkg`.
+Problem: We would need to solve how to apply the additional configuration (the two files above) to installed packages.
+
+From [#122512](https://developer.apple.com/forums/thread/122512):
+
+> I thought there might be some apple approved way of adding extra information to a package.
+
+>> No. The notarisation ticket covers the code signature of the package, and the code signature of the package covers the
+>> effective contents of that package. This is pretty much required. For example, one of your goals is to tweak the install scripts,
+>> but such scripts execute with enormous privileges and thus must be covered by the code signature, and thus covered by the notarisation ticket.
+
+---
+
+> Is there a Apple recommended method to provide custom packages for different customers?
+> Some way to add additional parameters to a package without breaking the notarizartion/signing of it?
+
+>> Option 2) Change your distribution strategy to distribute a static executable.
+>> Download any customer-specific resources and store them in /Library/Application Support.
+
+### Windows
+
+Currently we don't support signing of the MSI installers in `fleetctl package`. But this functionality could be added both to the command and the service.
+From Zach:
+> When we decide to support this (which we should do soon), we can use https://github.com/mtrojnar/osslsigncode.
+
+We should also research https://github.com/sassoftware/relic (it mentions Windows signature support)
+
+## Service Implementation
+
+It looks like we will be able to implement the first iteration of the Packager Service as a Go service running on a Linux server.
+The only limitation will be macOS stapling (see below).
+
+### Scale
+
+MVP of the service will support running one instance of the service. Probably ok as the majority of the load will be IO, network and disk.
+
+Nothing prevent us from horizontal scaling by sharding requests by `package_id` (or a `client_id`) in the future.
+
+### State storing
+
+For the first iteration, we should pick one of the following simple options:
+1. Store state in-memory. Simplest, but not resilient to crashes/restarts.
+2. Store state in an embedded disk database, such as:
+ - https://github.com/dgraph-io/badger (Pure Go)
+ - https://github.com/etcd-io/bbolt (Pure Go)
+ - Sqlite3 (Cgo 🙈), see https://www.youtube.com/watch?v=XcAYkriuQ1o
+
+- We already need disk storage for storing the packages, so option (2) is feasible to do from the get-go.
+
+### macOS requirements
+
+- Code Signing: Looks like https://github.com/sassoftware/relic would allow us to sign a `.pkg` in pure Go.
+- Notarization: We can implement a Go package that uses the new [Notary API](https://developer.apple.com/documentation/notaryapi).
+Limitation: Such API does not offer a way to "staple" the package, thus we would depend on the `stapler` tool for this last step (such tool is only available on macOS).
+So if we run the Packager service on a Linux server, we won't be able to support stapling. However, it seems stapling is recommended but not a must, see [#116812](https://developer.apple.com/forums/thread/116812).
+> If Gatekeeper can’t find a notarisation ticket stapled to the item, it attempts to get that ticket from the Apple notarisation servers.
+> Assuming the Mac is online, this typically works and thus the Gatekeeper check succeeds.
+
+### Windows requirements
+
+We need Docker to generate the MSI for Windows (the `fleetctl package` command uses the `fleetdm/wix:latest` docker image to generate them).
+We will need to pre-fetch such Docker Image during initialization (to not delay the first request indefinitely).
+
+PS: We could alternatively try a PoC that uses Wine+WiX on Linux without Docker?
+
+### Lambda?
+
+Depending on dependencies we could implement the service as a Lambda Function. Though we could make this a full service to not be tied to a specific provider.
+
+## Security / Threat model
+
+What could go wrong?
+
+### Fleet Credentials
+
+The MVP of the service will have access to:
+- Fleet's Developer Signing Key (for `.pkg` signing).
+- Fleet's Apple Connect Username and Password (for `.pkg` notarization).
+
+From Guillaume:
+> If someone manages to compromise this service, they could potentially sign AND notarize malware under Fleet's identity.
+
+Remediation: Fleet credentials should be stored on a secrets manager, e.g. [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html).
+
+### User Credentials
+
+The Fleet-URL and enroll secret are stored within the generated packages.
+If the unexpired installers are leaked, users' Fleet URLs and enroll secrets would be compromised.
+Attackers could enroll their devices to users' Fleet deployment.
+
+An attacker with access to an enroll secret can perform the following attacks:
+- Feed a Fleet server with fake data (possibly DoS the service).
+- Leak the queries configured in Fleet.
+
+Remediation: All generated packages should be securely deleted when they expire.
+
+## Sandbox/Demo & Cloud
+
+The design supports the following deployments:
+
+- Fleet On-Prem running with FleetDM's Packager and TUF.
+- Fleet On-Prem running with On-Prem Packager with FleetDM's TUF server.
+- Fleet On-Prem running with On-Prem Packager with On-Prem TUF server.
+- Fleet Sandbox/Demo & Cloud (basically all hosted by FleetDM).
+
+## New Fleetctl Command
+
+We could add new flags (e.g. `--remote`) to `fleetctl package` to generate the packages using a Packager Service instead of building locally.
\ No newline at end of file
diff --git a/proposals/Fleetctl-Docker-Image.md b/proposals/Fleetctl-Docker-Image.md
new file mode 100644
index 0000000000..d5f462aa45
--- /dev/null
+++ b/proposals/Fleetctl-Docker-Image.md
@@ -0,0 +1,51 @@
+# Goal
+
+We need `fleetctl package` functionality to generate all types of packages (PKG, MSI, DEB and RPM) from Linux.
+
+# How
+
+Create a new Docker image `fleetdm/fleetctl` that will contain `fleetctl` and all the dependencies ready to create packages.
+
+Users can then use the image to generate packages
+```sh
+$ docker run ... fleetdm/fleetctl:latest package --type={pkg|msi|deb|rpm} ...
+```
+
+## DEB and RPM
+
+DEB and RPM package generation is already native and no extra dependencies are required (uses https://github.com/goreleaser/nfpm).
+
+## MSI
+
+### Packaging
+
+We will need the same dependencies from `fleetdm/wix:latest` on the new `fleetdm/fleetctl:latest` image.
+
+### Signing (stretch goal)
+
+For `.msi` signing functionality:
+- The [relic](https://github.com/sassoftware/relic) tool seems to allow `.msi` signing (in Pure Go).
+- Alternatively, the [osslsigncode](https://github.com/mtrojnar/osslsigncode) tool could be embedded on the image.
+
+This is mentioned as a stretch goal because we currently don't have `.msi` signing functionality in `fleetctl package`.
+
+## PKG
+
+### Packaging
+
+To generate a `.pkg` we will need the same dependencies from `fleetdm/bomutils:latest` on the new `fleetdm/fleetctl:latest` image.
+
+### Signing
+
+The [relic](https://github.com/sassoftware/relic) tool seems to allow `.pkg` signing (in Pure Go).
+
+### Notarization
+
+#### Upload
+
+We can implement a Go package that uses the new [Notary API](https://developer.apple.com/documentation/notaryapi) to upload and notarize a `.pkg` (pure Go solution).
+
+#### No Stapling
+
+The Notary API currently does not offer a way to "staple" a package, and the `stapler` tool that allows this is only available on macOS.
+It seems stapling is recommended but not a must, see [#116812](https://developer.apple.com/forums/thread/116812).
\ No newline at end of file