An open API service indexing awesome lists of open source software.

https://github.com/mudler/poco

:ship: poCo - portable Containers. Create statically linked, portable binaries from container images (daemonless)
https://github.com/mudler/poco

Last synced: about 2 months ago
JSON representation

:ship: poCo - portable Containers. Create statically linked, portable binaries from container images (daemonless)

Awesome Lists containing this project

README

        




logo

poCo


Containers -> Binaries
Create statically linked, portable binaries from container images




license



go report card




A simple, static golang bundler!

poCo (_portable_-_Containers_) packs and converts container images into single, portable, statically linked binaries leveraging golang native `embed`.

## :question: How it works

`poCo` is extremely simple in the design.

`poCo` generates and builds golang code which embeds the container content compressed as an asset. It does bundle the assets by using the native golang `embed` primitives. The resulting binary on the first run will extract the content over the application `store` and will execute the entrypoint in a pivotroot environment, without requiring special permissions.

## :computer: Install

Download poCO from the [releases](https://github.com/mudler/poco/releases) and install it in your `PATH`. poCO releases are statically built, so no dependencies (besides `golang` to create `bundles`, are required)

## :running: Run

poCO is a no-frills binary bundler, we will see an example of how to bundle a container image into a binary.

Requires:

- `poco` installed
- `sudo`
- golang `>1.17` installed in the system where are you building

poCo bundles container images available remotely or locally (by specifying `--local` to the `bundle` subcommand).

For instance to pack the `alpine` image into a `sample` binary is as simple as:

```bash
CGO_ENABLED=0 ./poco bundle --image alpine --output sample
```

`CGO_ENABLED=0` will instruct the golang compiler behind the scenes to create a statically linked executable.

We can specify optionally a default entrypoint for the resulting binary with `--entrypoint`, which is by default set to `/bin/sh`.

You can run the `--help` subcommand on `sample` to inspect its output, and you will see there are available few options:

```
❯ ./sample --help
NAME:
sample - sample

USAGE:
[global options] command [command options] [arguments...]

VERSION:
0.1

DESCRIPTION:
sample

AUTHOR:
sample

COMMANDS:
exec
uninstall
help, h Shows a list of commands or help for one command

GLOBAL OPTIONS:
--store value Default application store. Empty for TMPDIR
--entrypoint value Default application entrypoint (default: "/bin/sh")
--add-mounts value Additional mountpoints
--mounts value Default app mountpoints (default: "/sys", "/tmp", "/run")
--help, -h show help
--version, -v print the version
```

The binary has some defaults that can be override during build time with `bundle`, to run the application (in our case, `sh` from alpine), just run:

```
./sample # spawns a new /bin/sh shell
```

You can also pass all the args to the entrypoint of the binary (`/bin/sh`), by specifying `-`:

```
./sample - -c "echo foo"
```

See the `example/` folder for a more complete example.

Supports: `CGO_ENABLED`, `GOOS`, `GOARCH`, etc.

It can target all architectures supported by golang.

### Github Action

To run it with Github actions, see [https://github.com/mudler/poco-github-action](https://github.com/mudler/poco-github-action)

### `bundle`

`bundle` creates a binary from a container image, it takes several options listed here:

| Flag | Description |
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| --entrypoint | Default binary entrypoint. This is the first binary from the container image which will be executed. It defaults to `/bin/sh` |
| --output | Default binary output location |
| --compression | Compression format used to pack the container image into the bundle. Supported formats: (bz2, zst, gz, xz, lz4, br, sz) |
| --app-description | This is the description of the app that will be displayed in the resulting binary `--help` |
| --app-author | This is the author of the app that will be displayed in the resulting binary `--help` |
| --app-name | This is the name of the app that will be displayed in the resulting binary `--help` |
| --app-version | This is the version of the app that will be displayed in the resulting binary `--help`. The version will be used between different binary bundles to handle upgrades. |
| --local | Tells poco to get the container image from the local Docker daemon instead of fetching it remotely. By default poco doesn't require a Docker daemon running locally |
| --app-mounts | A list of default mount binding for the app. The application runs in a chroot-alike environment, without access to the files of the system unless explictly mounted. Multiple mounts can be specified. |
| --app-store | A default store for your app. This is where the bundle gets extracted before being executed, and where the real app data lives afterward on subsequent calls. |
| --image | The container image to bundle. |
| --command-prefix | Command prefix for auto-generated code. Usually you don't need to change that unless you are running the builds as root |
| --directory | A directory to bundle (in place of the container image) |

#### Mounts

A `poCo` bundle runs in a sandboxed environment. To expose directories or files, the resulting binary in runtime tales the `--mounts` or `--add-mounts` option (also multiple times) to specify a list of directories or files to expose from the host environment.

While creating the binary, it is also possible to specify a default set, so the binary runs with the directory shared from the host already, this is possible by passing `--app-mounts`.

For instance, consider:

```bash
CGO_ENABLED=0 ./poco bundle --image alpine --output sample --app-mounts /tmp --app-mounts 'ro:/home/.bar:/home/.bar'
```

will create a `sample` binary with `alpine` which `/tmp` will be mapped `rw` and `/home/.bar` `ro` from the host.

#### Default store

Every application has a default store. By default, each application will unpack its content to a temporary directory. To change this behavior and persist data in the system which is running the app, specify a default location with `--app-store`.

For instance, the following will use the `~/.poco/alpine` folder to unpack the bundle content on the first run:
```bash
CGO_ENABLED=0 ./poco bundle --image alpine --output sample --app-store '$HOME/.poco/alpine'
```

Every application can indeed be uninstalled, which just deletes the default `app-store`:

```bash
./sample uninstall
```

#### Metadata

Every generated bundle will have a default --help which is being displayed. It is possible to set metadata such as `description`, `name`, `author`, `copyright` that will be automatically available in the resulting binary `--help`.

The version is more relevant if a default `--app-store` is being specified. The `app-version` is used during the first run to determine if the installed bundle should be replaced or not.

### `render`

`render` allows to render the generated golang code into a specified directory. This is might be helpful if you want to change the generated binary before build.

```
$ mkdir alpine
$ ./poco render --image alpine alpine
$ ls alpine/
go.mod main.go
```

### `pack`

`pack` is an internal utility to pack directories as container images that can be `docker` loaded afterwards:

```
$ mkdir foo
$ touch foo/bar
$ poco pack myimage:tag foo --destination output.tar
$ docker load -i output.tar
$ docker push myimage:tag
```

### `extract`

`extract` is an internal utility to scan a binary and all its dynamic linked libraries. It will copy the binary and the libraries needed by it into the specified folder, respecting the path hierarchy.

```
$ poco extract /my/dynamic/bin /output
$ poco pack myimage:tag /output --destination image.tar
$ docker load -i image.tar
...
```

### `pack-assets`

`pack-assets` is an internal utility to pack assets for the bundle.

```
$ mkdir foo
$ touch foo/bar
$ poco pack-assets -C foo .
$ ls
assets.tar.xz

```

### `unpack`

`unpack` is an internal utility to unpack a container image into a directory

```
$ mkdir alpine
$ poco unpack alpine alpine
$ ls alpine
bin etc usr ...
$ poco bundle --directory alpine ...
```

## :notebook: Troubleshooting

When troubleshooting issues with bundles created by `poco`, it might be helpful to open a shell within a bundle:

```bash
./ --entrypoint /bin/sh
```

The `--entrypoint` command is available in all binaries generated by `poco` and as such you can override the default entrypoint anytime.

_NOTE_ The `--mounts` command is also available in all binaries generated by `poco` and will **OVERRIDE** the default mount points specified during bundle time. To have additional mounts besides default, use `--add-mounts` instead.

## :warning: Notes

- During build sudo is required in order to preserve container permissions.
- By default bundles do have network access, but as Docker images mount during build /etc/resolv.conf, the file is empty in docker images, which results in no ability to resolv network address. In order to let the bundle read the `/etc/resolv.conf` from the host, use `--app-mounts /etc/resolv.conf`.

## :mag: Examples

See the `examples` folder or [linuxbundles](https://mudler.github.io/linuxbundles/) for a collection of popular distributions packaged or either [caramel](https://mocaccinoos.github.io/caramel/) for a more complete example involving popular apps.

The pipeline builds the `firefox` image which can be downloaded and to run locally as a standard binary:

```
./firefox
```
To uninstall:

```
./firefox uninstall
```

## :notebook: TODO

- [ ] Multi-platform support (Windows, MacOS at least..)

## :question: Why?

¯\_(ツ)_/¯

Someone told me this wasn't possible, so here we are.

I know there is flatpak and also tons of AppImages out there and this is just yet another bundler for most of you, so to make it clear: the scope of this project is not even comparable to them.

While I was sketching this up I realized I wanted something VERY simple that doesn't gets in the way and opinionated enough that can leverage already existing container image - without the need of additional docs or specific procedures for users.
This bundler might fit just simple and specific purposes - and most likely - people like me that doesn't have high end goals and rely on golang daily.

So focus of this project is - to prove a point of course - and on semplicity and portability rather than, for example, security.

And besides, let's be frank. Building bundles with go+docker is really easy to go with as a stack.

## :notebook: Credits

Docker authors for the pivotroot code part, was very helpful read to get that right.

# 🐜 Contribution

You can improve this project by contributing in following ways:

- report bugs
- fix issues
- request features
- asking questions (just open an issue)

and any other way if not mentioned here.

## :notebook: Author

poCo is released under GPL-3, Copyright Ettore Di Giacinto