README
¶
gaoler
CLI for hardened container sandboxes with a customisable image.
gaol /jāl/
A place of confinement, especially for minor offenses or provisional imprisonment; a jail.
gaoler is a command-line tool for creating and running hardened container sandboxes
for running commands and workloads isolated from the main system, while providing
an easy-to-use interface to manage these.
About
All containers are created from a base container image which is built and managed by the application.
Each container created with gaoler has the following settings to achieve
hardening:
sudois only permitted duringinit(--setup) andsetup- The moby default seccomp profile
- Removes Linux capabilities through
cap_drop=ALL - Security option:
no-new-privileges - Supported images assumes a non-root user
- Limits process count
- Default limits on CPU and memory
By default it mounts the current working directory into /home/<user>/workspaces/<dir>
with support for additional workspaces and mounts.
Examples on configuration and image templates can be found in examples.
Concepts
Base image
By default new containers are created from a base image managed by the application.
This base image can be modified without rebuilding by using setup (or flag --setup
during init), and these changes can be saved in the base image with commit.
There are however several options on images for containers, see the section Image customisation for more details.
Containers
Each new container is created with the hardening described in the About section and are by default created by the applicaiton managed base image.
These containers can be further customised with environment variables, mounts and available resources through a configuration file and flags at startup.
The configuration file provides means to both set global container
settings through the top-level container object and through
profiles; which can be entirely independent from the top-level object,
or they can inherit and override settings (including environment
variables ad mounts) from the top-level configuration.
Workspaces
By default it mounts the current working directory into /home/<user>/workspaces/<dir>
with support for additional workspaces.
When providing multiple workspaces with the flag --workspace, the target
"main" working directory must be specified with the first --workspace flag.
If no workspace is wanted for a new container instance the flag --no-workspace
can be provided.
Environment variables
Environment variables can be set through either configuration, or provided with flags at startup. If both are used the results will be merged with the flags taking precedence if there are any conflicts.
Depending on where the environment variables are set, various interpolation and evaluation is done on them.
Configuration:
container.env / profiles.profile-name.env:
A value can be set directly, or interpolation can be leveraged:
- Value:
{env:ENV_VAR}: Evaluate an environment variable on the host and set it to the environment variable in the container. - Value:
{file:/path/to/file}: Reads a file and set the contents of the file to the environment variable in the container.
container.envFile / profiles.profile-name.envFile:
Reads a file with environment variables with key-value pairs where the key is the environment variable name, and the value is the value of the environment variable.
The result is merged into container.env / profiles.profile-name.env.
If a value is not set for a key it will attempt to read the environment variable from the host.
Format for file:
ENV_VAR_1=value1
ENV_VAR_2=value2
ENV_VAR_3
Flags:
Flag --env / -e can use used like so:
--env ENV_VAR=value: Set the environment variable to the provided value--env ENV_VAR: If an environment variable on the local host has that name it will be provided to the container.
Mounts
Mounts can be set through either configuration, or provided with flags at startup. If both are used the results will be merged with the flags taking precedence if there are any conflicts.
Mounts set in configuration supports various ways of interpolation.
Destination paths with suffix :ro will be set to read-only.
Configuration
container.mounts / profiles.profile-name.mounts
These are set as a key and a value where the key is the source path on the local host, and the value is the destination path in the container.
Interpolation is supported through:
~: The home directory. When specified in the source path it resolves to the home directory on the host, and if set in the destination path it resolves to the home directory in the container.{HOME}: The home directory. When specified in the source path it resolves to the home directory on the host, and if set in the destination path it resolves to the home directory in the container.{USER}: The user. When specified in the source path it resolves to the user on the host, and if set in the destination path it resolves to the user in the container.
Flags
Flag --mounts / -m can use used like so:
/local/path:/container/path/local/path:/container/path:ro(set to read-only)
Profiles
Profiles provide an additional way to customise containers for different scenarios and/or setups.
Profiles work together with, and as an extension to the global container settings.
Profiles can inherit resource limits (if they are not set),
and mounts and environment variables with the field inherit: true
Further details can be found at the profiles section of the configuration section.
Install
[!NOTE] An install script will be available at a later time.
Prerequisites:
- Docker Engine
Linux
Go to Releases and download the latest release for Linux:
# This example uses architecture amd64 as an example, if you are using arm64, change it.
curl -sLO https://codeberg.org/KarlGW/gaoler/releases/download/v0.8.0/gaoler-0.8.0-linux-amd64.tar.gz
mkdir -p ~/.local/bin
tar -C ~/.local/bin -xvf gaoler-0.8.0-linux-amd64.tar.gz && rm gaoler-0.8.0-linux-amd64.tar.gz
[!NOTE] Make sure
~/.local/binis set to$PATH.
macOS
Go to Releases and download the latest release for macOS:
# This example uses architecture arm64 as an example, if you are using amd64, change it.
curl -sLO https://codeberg.org/KarlGW/gaoler/releases/download/v0.8.0/gaoler-0.8.0-darwin-arm64.tar.gz
mkdir -p ~/.local/bin
tar -C ~/.local/bin -xvf gaoler-0.8.0-darwin-arm64.tar.gz && rm gaoler-0.8.0-darwin-arm64.tar.gz
[!NOTE] Make sure
~/.local/binis set to$PATH.
Windows
Go to Releases and download the latest release for Windows.
Unzip it into a directory/add directory to $PATH.
go install
go install codeberg.org/KarlGW/gaoler/cmd/gaoler@latest
Setup autocompletion
To enable auto/tab completion for gaoler follow the steps below depending on shell.
Bash
Current session:
gaoler source <(gaoler completion bash)
For all sessions:
echo -e "\n# gaoler\nsource <(gaoler completion bash)" >> ~/.bashrc
Zsh
Current session:
source <(gaoler completion zsh)
For all sessions:
echo -e "\n# gaoler\nsource <(gaoler completion zsh)" >> ~/.zshrc
PowerShell
First create the autocompletion script:
./gaoler completion powershell >> "$(Split-Path $PROFILE)/gaoler.ps1"
Current session:
& "$(Split-Path $PROFILE)/gaoler.ps1"
For all sessions:
"& $(Split-Path $PROFILE)/gaoler.ps1" >> $PROFILE
Quick start
-
Initialise and build the container:
gaoler init -
Run a container:
gaoler run
This will start a container that mounts the current working directory
into the path /home/<user>/workspaces/<dir>.
[!TIP] Detach from a container with
CTRL+P,CTRL+Q.
[!NOTE] Each subsequent call to run will create a new container named:
gaoler-<short-uuid>.
For additional information on use run:
gaoler --help
And:
gaoler <command> --help
Coding agents
As gaoler provides hardened containers it's ideal to use together with
coding agents. Check out Coding agents over at the example section
for more detailed information.
Configuration
The configuration file is located at ~/.gaoler/config.json.
This can be used to configure container image settings and container configuration that should apply to all new containers.
Many of these can be set at run time when handling containers.
Supported settings (default settings shown):
{
"image": {
"rootCommands": [],
"userCommands": [],
"template": ""
},
"container": {
"env": {},
"envFile": "",
"mounts": {},
"cpus": 1,
"memory": "2g",
"maxPids": 100
},
"profiles": {},
"endpoint": ""
}
[!NOTE]
endpointis automatically populated at first run. If connection to the Docker host cannot be done, this needs manual configuration.
Example configuration:
{
"image": {
"rootCommands": [
"RUN <command>"
],
"userCommands": [
"RUN <command>"
],
},
"container": {
"env": {
"ENV_VAR": "value"
},
"envFile": "/path/to/env-file",
"mounts": {
"/path/to/local/dir": "/path/to/container/dir"
},
"cpus": 1,
"memory": "2g",
"maxPids": 100
},
"profiles": {
"profile-name": {
"env": {
"ENV_VAR": "value"
},
"envFile": "/path/to/env-file",
"mounts": {
"/path/to/local/dir": "/path/to/container/dir"
},
"cpus": 2,
"memory": "4g",
"maxPids": 100,
"inherit": false
}
},
"endpoint": "unix:///var/run/docker.sock"
}
image
Contains configuration for the container image.
image.rootCommands: Dockerfile instructions run as root.
Example:
[
"RUN <command>",
"RUN <command>"
]
image.userCommands: Dockerfile instructions run as user.
Example:
[
"RUN <command>",
"RUN <command>"
]
image.template: Path to a custom template.
Supports ~, {HOME} and {USER} to resolve user home directory and username.
container
Contains configuration for each instance of a running container.
container.env: Defined as key-value pairs where the key
is the environment variable name, and the value is the
value of the environment variable.
Supports interpolation through:
- Value:
{env:ENV_VAR}: Evaluate an environment variable on the host and set it to the environment variable in the container. - Value:
{file:/path/to/file}: Reads a file and set the contents of the file to the environment variable in the container.
container.envFile: Reads a file with environment variables
with key-value pairs where the key is the environment
variable name, and the value is the value of the
environment variable.
The result is merged into container.env.
If a value is not set for a key it will attempt to read the environment variable from the host.
Format for file:
ENV_VAR_1=value1
ENV_VAR_2=value2
ENV_VAR_3
Lines with prefix # or // are ignored.
container.mounts: Defined as key-value pairs where the key
is he source (local) path, and the value is the destination
(container) path.
Example:
{
"/path/to/local/dir1": "/path/to/container/dir1",
"/path/to/local/dir2": "/path/to/container/dir2:ro"
}
Supports ~, {HOME} and {USER} to resolve user home directory and username.
The local path resolves to the local home directory.
The destination path resolves to the container home directory.
The suffix :ro makes the mount read-only.
container.cpus: Number of CPUs available to the container.
container.memory: Memory limit of the contaier. Is specified
as a string. As an example 1 GiB would be specified as 1g,
and 2 GiB would be specified as 2g.
container.maxPids: Number of maximum process IDs (processes)
that can run in the container.
Container configuration profiles. Provides possibilities to setup
multiple container configurations that can be through providing
the flag --profile to run.
The key is the profile name, and the value is the container configuration.
Example:
{
"profiles": {
"profile-name": {
"env": {
"ENV_VAR": "value"
},
"envFile": "/path/to/env-file",
"mounts": {
"/path/to/local/dir": "/path/to/container/dir"
},
"cpus": 1,
"memory": "2g",
"maxPids": 100
"inherit": false,
}
}
}
profiles.profile-name
Has the same fields as container with the added field inherit.
profiles.profile-name.inherit:
Set to true to inherit settings from the global container configuration.
Values set in the profile will take precedence.
Undefined inherit is the same as false.
envfrom global container configuration will be merged into the ones set on the profilemountsfrom global container configuration will be merged into the ones set on the profile.imagewill be inherited from the global configuration if NOT set in the profilecpuswill be inherited from the global configuration if NOT set in the profilememorywill be inherited from the global configuration if NOT set in the profilemaxPidswill be inherited from the global configuration if NOT set in the profile
endpoint
Docker host endpoint (unix socket). Automatically populated at first run. If connection to the Docker host cannot be done, this needs manual configuration.
Command reference
This section provides information about the available commands. For further
details run gaoler <command> --help.
init
Initialise and build the container image. Is required before running any container.
With flag --setup an interactive shell session is started after image build
has completed to allow for additional setup tasks. See setup for additional
information.
Supports to build and restore a user home directory with the --restore flag.
[!NOTE] Not required if an image have been built with
docker buildand labeled withgaoler=<version>.
setup
Enter an interactive setup session for the container image. To persist
changes done during this session run commit when done.
commit
Commit changes performed during setup or init(with --setup flag).
backup
Backup user home directory. Default backup location is ~/.gaoler/backup
restore
Restore user home directory. Uses latest backup by default.
copy
Copy files from/to a container.
config
Not implemented
run
Create and start a container. Enters an interactive session unless flag --detach is specified.
To detach and keep the container running without a running program provide flag --tty.
Provides many customisation options, see run --help for more.
Detach from active session with CTRL+P, CTRL+Q.
exec
Run a command in a new process in an already running container.
Detach from active session with CTRL+P, CTRL+Q.
attach
Attach to a container after a detach, either from run/exec with flag --detach, or after
providing CTRL+P, CTRL+Q.
list
List containers managed by gaoler.
remove
Remove containers managed by gaoler.
image
image list
List images managed by gaoler.
image remove
Remove images managed by gaoler. Removing an image will remove any containers,
if any, associated with that image.
Image customisation
The container image can be customised in multiple ways:
- At image build
- At
init - With
setup - A template
- An external image labeled with
gaoler
[!NOTE]
sudocan only be used duringinitorsetup.
Customise at image build
Customising the image during build keeps the image customisation consistent between builds and keeps the amount of image layers down.
This can be achieved with either setting rootCommands and userCommands
in ~/.gaoler/config.json or provifing the flags --root-commands and
--user-commands together with init.
What this does is to add instructions in the Dockefile used for
the image.
The root commands are run as root after the initial base setup is done.
The user commands are run as the configured user after it has been created and set as the current user.
Example:
FROM ubuntu:24.04
RUN apt-get update \
&& apt-get install -y \
curl \
git \
sudo \
vim
# Root commands will be added here.
# ... ...
RUN useradd \
--uid 10001 \
--user-group \
--create-home \
--shell /bin/bash \
<user> \
&& usermod -a -G sudo <user> \
&& cp /etc/sudoers /etc/sudoers.tmp \
&& echo "<user> ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.tmp \
&& mv /etc/sudoers.tmp /etc/sudoers
USER <user>
WORKDIR /home/<user>
# User commands will be added here.
# ... ...
Customise at init
During the default behaviour of init an interactive session to a setup
container will be started. This provides an opportunity to perform
administrative tasks and other setup.
Afterwards run commit to commit the changes to the container image.
Customise with setup
Running setup will create a setup container. As with the interactive
init, administrative tasks and setup can be done.
Afterwards run commit to commit the changes to the container image.
Custom template
A custom template can be provided with either the configuration file or a command line flag.
Example template:
FROM {{.BaseImage}}
# Commands
{{range .RootCommands}}
{{.}}
{{end}}
{{template "useradd" .}}
{{range .UserCommands}}
{{.}}
{{end}}
{{- end -}}
External image
An external image (built with docker build as an example) needs the following:
- A user with
sudoprivileges - Be labeled with
gaoler:<version>(ifgaolershould be able to manage it)
TODO
- Support for other "built-in" base images than ubuntu
- Support for custom username in container
- Support for no network
- Proxy side car for HTTP/HTTPS
- Proxy-command side car for commands