docker("network ls --filter driver='macvlan' --format='{{.Name}}'"); $subnet = ['bridge'=>'', 'host'=>'', 'none'=>'']; foreach ($custom as $network) $subnet[$network] = substr($DockerClient->docker("network inspect --format='{{range .IPAM.Config}}{{.Subnet}}, {{end}}' $network"),0,-1); function stopContainer($name) { global $DockerClient; $waitID = mt_rand(); echo "

"; echo "\n"; @flush(); $retval = $DockerClient->stopContainer($name); $out = ($retval === true) ? "Successfully stopped container '$name'" : "Error: ".$retval; echo "\n"; @flush(); } function removeContainer($name, $cache=false) { global $DockerClient; $waitID = mt_rand(); echo "

"; echo "\n"; @flush(); $retval = $DockerClient->removeContainer($name, false, $cache); $out = ($retval === true) ? "Successfully removed container '$name'" : "Error: ".$retval; echo "\n"; @flush(); } function removeImage($image) { global $DockerClient; $waitID = mt_rand(); echo "

"; echo "\n"; @flush(); $retval = $DockerClient->removeImage($image); $out = ($retval === true) ? "Successfully removed image '$image'" : "Error: ".$retval; echo "\n"; @flush(); } function pullImage($name, $image) { global $DockerClient, $DockerTemplates, $DockerUpdate; $waitID = mt_rand(); if (!preg_match("/:\S+$/", $image)) $image .= ":latest"; echo "

"; echo "\n"; @flush(); $alltotals = []; $laststatus = []; $strError = ''; $DockerClient->pullImage($image, function ($line) use (&$alltotals, &$laststatus, &$waitID, &$strError, $image, $DockerClient, $DockerUpdate) { $cnt = json_decode($line, true); $id = (isset($cnt['id'])) ? trim($cnt['id']) : ''; $status = (isset($cnt['status'])) ? trim($cnt['status']) : ''; if (isset($cnt['error'])) { $strError = $cnt['error']; } if ($waitID !== false) { echo "\n"; @flush(); $waitID = false; } if (empty($status)) return; if (!empty($id)) { if (!empty($cnt['progressDetail']) && !empty($cnt['progressDetail']['total'])) { $alltotals[$id] = $cnt['progressDetail']['total']; } if (empty($laststatus[$id])) { $laststatus[$id] = ''; } switch ($status) { case 'Waiting': // Omit break; case 'Downloading': if ($laststatus[$id] != $status) { echo "\n"; } $total = $cnt['progressDetail']['total']; $current = $cnt['progressDetail']['current']; if ($total > 0) { $percentage = round(($current / $total) * 100); echo "\n"; } else { // Docker must not know the total download size (http-chunked or something?) // just show the current download progress without the percentage $alltotals[$id] = $current; echo "\n"; } break; default: if ($laststatus[$id] == "Downloading") { echo "\n"; } if ($laststatus[$id] != $status) { echo "\n"; } break; } $laststatus[$id] = $status; } else { if (strpos($status, 'Status: ') === 0) { echo "\n"; } if (strpos($status, 'Digest: ') === 0) { $DockerUpdate->setUpdateStatus($image, substr($status, 8)); } } @flush(); }); echo "\n"; @flush(); if (!empty($strError)) { echo "\n"; @flush(); return false; } return true; } function xml_encode($string) { return htmlspecialchars($string, ENT_XML1, 'UTF-8'); } function xml_decode($string) { return strval(html_entity_decode($string, ENT_XML1, 'UTF-8')); } function postToXML($post, $setOwnership=false) { $dom = new domDocument; $dom->appendChild($dom->createElement("Container")); $xml = simplexml_import_dom($dom); $xml['version'] = 2; $xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName'])); $xml->Repository = xml_encode(trim($post['contRepository'])); $xml->Registry = xml_encode(trim($post['contRegistry'])); $xml->Network = xml_encode($post['contNetwork']); $xml->MyIP = xml_encode($post['contMyIP']); $xml->Privileged = strtolower($post['contPrivileged'])=='on' ? 'true' : 'false'; $xml->Support = xml_encode($post['contSupport']); $xml->Project = xml_encode($post['contProject']); $xml->Overview = xml_encode($post['contOverview']); $xml->Category = xml_encode($post['contCategory']); $xml->WebUI = xml_encode(trim($post['contWebUI'])); $xml->TemplateURL = xml_encode($post['contTemplateURL']); $xml->Icon = xml_encode(trim($post['contIcon'])); $xml->ExtraParams = xml_encode($post['contExtraParams']); $xml->PostArgs = xml_encode($post['contPostArgs']); $xml->DateInstalled = xml_encode(time()); $xml->DonateText = xml_encode($post['contDonateText']); $xml->DonateLink = xml_encode($post['contDonateLink']); $xml->DonateImg = xml_encode($post['contDonateImg']); $xml->MinVer = xml_encode($post['contMinVer']); // V1 compatibility $xml->Description = xml_encode($post['contOverview']); $xml->Networking->Mode = xml_encode($post['contNetwork']); $xml->Networking->addChild("Publish"); $xml->addChild("Data"); $xml->addChild("Environment"); $size = is_array($post['confName']) ? count($post['confName']) : 0; for ($i = 0; $i < $size; $i++) { $Type = $post['confType'][$i]; $config = $xml->addChild('Config', xml_encode($post['confValue'][$i])); $config['Name'] = xml_encode($post['confName'][$i]); $config['Target'] = xml_encode($post['confTarget'][$i]); $config['Default'] = xml_encode($post['confDefault'][$i]); $config['Mode'] = xml_encode($post['confMode'][$i]); $config['Description'] = xml_encode($post['confDescription'][$i]); $config['Type'] = xml_encode($post['confType'][$i]); $config['Display'] = xml_encode($post['confDisplay'][$i]); $config['Required'] = xml_encode($post['confRequired'][$i]); $config['Mask'] = xml_encode($post['confMask'][$i]); // V1 compatibility if ($Type == 'Port') { $port = $xml->Networking->Publish->addChild("Port"); $port->HostPort = $post['confValue'][$i]; $port->ContainerPort = $post['confTarget'][$i]; $port->Protocol = $post['confMode'][$i]; } elseif ($Type == 'Path') { $path = $xml->Data->addChild("Volume"); $path->HostDir = $post['confValue'][$i]; $path->ContainerDir = $post['confTarget'][$i]; $path->Mode = $post['confMode'][$i]; } elseif ($Type == 'Variable') { $variable = $xml->Environment->addChild("Variable"); $variable->Value = $post['confValue'][$i]; $variable->Name = $post['confTarget'][$i]; $variable->Mode = $post['confMode'][$i]; } } $dom = new DOMDocument('1.0'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->loadXML($xml->asXML()); return $dom->saveXML(); } function xmlToVar($xml) { global $subnet; $xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml); $out = []; $out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name)); $out['Repository'] = xml_decode($xml->Repository); $out['Registry'] = xml_decode($xml->Registry); $out['Network'] = xml_decode($xml->Network); $out['MyIP'] = xml_decode($xml->MyIP ?? ''); $out['Privileged'] = xml_decode($xml->Privileged); $out['Support'] = xml_decode($xml->Support); $out['Project'] = xml_decode($xml->Project); $out['Overview'] = stripslashes(xml_decode($xml->Overview)); $out['Category'] = xml_decode($xml->Category); $out['WebUI'] = xml_decode($xml->WebUI); $out['TemplateURL'] = xml_decode($xml->TemplateURL); $out['Icon'] = xml_decode($xml->Icon); $out['ExtraParams'] = xml_decode($xml->ExtraParams); $out['PostArgs'] = xml_decode($xml->PostArgs); $out['DonateText'] = xml_decode($xml->DonateText); $out['DonateLink'] = xml_decode($xml->DonateLink); $out['DonateImg'] = xml_decode($xml->DonateImg ?? $xml->DonateImage); // Various authors use different tags. DonateImg is the official spec $out['MinVer'] = xml_decode($xml->MinVer); $out['Config'] = []; if (isset($xml->Config)) { foreach ($xml->Config as $config) { $c = []; $c['Value'] = strlen(xml_decode($config)) ? xml_decode($config) : xml_decode($config['Default']); foreach ($config->attributes() as $key => $value) { $value = xml_decode($value); $val = strtolower($value); if ($key == 'Mode') { switch (xml_decode($config['Type'])) { case 'Path': $value = ($val=='rw'||$val=='rw,slave'||$val=='rw,shared'||$val=='ro'||$val=='ro,slave'||$val=='ro,shared') ? $value : "rw"; break; case 'Port': $value = ($val=='tcp'||$val=='udp') ? $value : "tcp"; break; } } $c[$key] = $value; } $out['Config'][] = $c; } } // some xml templates advertise as V2 but omit the new element // check for and use the V1 element when this occurs if (empty($out['Network']) && isset($xml->Networking->Mode)) { $out['Network'] = xml_decode($xml->Networking->Mode); } // check if network exists if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none'; // V1 compatibility if ($xml['version'] != '2') { if (isset($xml->Description)) { $out['Overview'] = stripslashes(xml_decode($xml->Description)); } if (isset($xml->Networking->Publish->Port)) { $portNum = 0; foreach ($xml->Networking->Publish->Port as $port) { if (empty(xml_decode($port->ContainerPort))) continue; $portNum += 1; $out['Config'][] = [ 'Name' => "Host Port ${portNum}", 'Target' => xml_decode($port->ContainerPort), 'Default' => xml_decode($port->HostPort), 'Value' => xml_decode($port->HostPort), 'Mode' => xml_decode($port->Protocol) ? xml_decode($port->Protocol) : "tcp", 'Description' => ($out['Network'] == 'bridge') ? 'Container Port: '.xml_decode($port->ContainerPort) : 'n/a', 'Type' => 'Port', 'Display' => 'always', 'Required' => 'true', 'Mask' => 'false' ]; } } if (isset($xml->Data->Volume)) { $volNum = 0; foreach ($xml->Data->Volume as $vol) { if (empty(xml_decode($vol->ContainerDir))) continue; $volNum += 1; $out['Config'][] = [ 'Name' => "Host Path ${volNum}", 'Target' => xml_decode($vol->ContainerDir), 'Default' => xml_decode($vol->HostDir), 'Value' => xml_decode($vol->HostDir), 'Mode' => xml_decode($vol->Mode) ? xml_decode($vol->Mode) : "rw", 'Description' => 'Container Path: '.xml_decode($vol->ContainerDir), 'Type' => 'Path', 'Display' => 'always', 'Required' => 'true', 'Mask' => 'false' ]; } } if (isset($xml->Environment->Variable)) { $varNum = 0; foreach ($xml->Environment->Variable as $varitem) { if (empty(xml_decode($varitem->Name))) continue; $varNum += 1; $out['Config'][] = [ 'Name' => "Key ${varNum}", 'Target' => xml_decode($varitem->Name), 'Default' => xml_decode($varitem->Value), 'Value' => xml_decode($varitem->Value), 'Mode' => '', 'Description' => 'Container Variable: '.xml_decode($varitem->Name), 'Type' => 'Variable', 'Display' => 'always', 'Required' => 'false', 'Mask' => 'false' ]; } } } xmlSecurity($out); return $out; } function xmlSecurity(&$template) { foreach ($template as &$element) { if ( is_array($element) ) { xmlSecurity($element); } else { if ( is_string($element) ) { $tempElement = htmlspecialchars_decode($element); $tempElement = str_replace("[","<",$tempElement); $tempElement = str_replace("]",">",$tempElement); if ( preg_match('#(.*?)#is',$tempElement) || preg_match('#(.*?)#is',$tempElement) ) { $element = "REMOVED"; } } } } } function xmlToCommand($xml, $create_paths=false) { global $var; global $docroot; $xml = xmlToVar($xml); $cmdName = strlen($xml['Name']) ? '--name='.escapeshellarg($xml['Name']) : ''; $cmdPrivileged = strtolower($xml['Privileged'])=='true' ? '--privileged=true' : ''; $cmdNetwork = '--net='.escapeshellarg(strtolower($xml['Network'])); $cmdMyIP = $xml['MyIP'] ? '--ip='.escapeshellarg($xml['MyIP']) : ''; $Volumes = ['']; $Ports = ['']; $Variables = ['']; $Devices = ['']; // Bind Time $Variables[] = 'TZ="' . $var['timeZone'] . '"'; // Add HOST_OS variable $Variables[] = 'HOST_OS="unRAID"'; foreach ($xml['Config'] as $key => $config) { $confType = strtolower(strval($config['Type'])); $hostConfig = strlen($config['Value']) ? $config['Value'] : $config['Default']; $containerConfig = strval($config['Target']); $Mode = strval($config['Mode']); if ($confType != "device" && !strlen($containerConfig)) continue; if ($confType == "path") { $Volumes[] = escapeshellarg($hostConfig).':'.escapeshellarg($containerConfig).':'.escapeshellarg($Mode); if ( ! file_exists($hostConfig) && $create_paths ) { @mkdir($hostConfig, 0777, true); @chown($hostConfig, 99); @chgrp($hostConfig, 100); } } elseif ($confType == 'port') { // Export ports as variable if Network is set to host if (preg_match('/^(host|eth[0-9]|br[0-9]|bond[0-9])/',strtolower($xml['Network']))) { $Variables[] = strtoupper(escapeshellarg($Mode.'_PORT_'.$containerConfig).'='.escapeshellarg($hostConfig)); // Export ports as port if Network is set to bridge } elseif (strtolower($xml['Network'])== 'bridge') { $Ports[] = escapeshellarg($hostConfig.':'.$containerConfig.'/'.$Mode); // No export of ports if Network is set to none } } elseif ($confType == "variable") { $Variables[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig); } elseif ($confType == "device") { $Devices[] = escapeshellarg($hostConfig); } } $postArgs = explode(";",$xml['PostArgs']); $cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s', $cmdName, $cmdNetwork, $cmdMyIP, $cmdPrivileged, implode(' -e ', $Variables), implode(' -p ', $Ports), implode(' -v ', $Volumes), implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $postArgs[0]); return [preg_replace('/\s+/', ' ', $cmd), $xml['Name'], $xml['Repository']]; } function execCommand($command) { // $command should have all its args already properly run through 'escapeshellarg' $descriptorspec = [ 0 => ['pipe', 'r'], // stdin is a pipe that the child will read from 1 => ['pipe', 'w'], // stdout is a pipe that the child will write to 2 => ['pipe', 'w'] // stderr is a pipe that the child will write to ]; $id = mt_rand(); echo '

'; echo ''; @flush(); $proc = proc_open($command." 2>&1", $descriptorspec, $pipes, '/', []); while ($out = fgets( $pipes[1] )) { $out = preg_replace("%[\t\n\x0B\f\r]+%", '', $out); echo ''; @flush(); } $retval = proc_close($proc); echo ''; $out = $retval ? 'The command failed.' : 'The command finished successfully!'; echo ''; return $retval===0; } function getXmlVal($xml, $element, $attr=null, $pos=0) { $xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml); $element = $xml->xpath("//$element")[$pos]; return isset($element) ? (isset($element[$attr]) ? strval($element[$attr]) : strval($element)) : ""; } function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) { $xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml); $element = $xml->xpath("//$el")[$pos]; if (!isset($element)) $element = $xml->addChild($el); if ($attr) { $element[$attr] = $value; } else { $element->{0} = $value; } $dom = new DOMDocument('1.0'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->loadXML($xml->asXML()); $xml = $dom->saveXML(); } function getAllocations() { global $DockerClient, $host; foreach ($DockerClient->getDockerContainers() as $ct) { $list = $port = []; $nat = $ip = false; $list['Name'] = $ct['Name']; foreach ($ct['Ports'] as $tmp) { $nat = $tmp['NAT']; $ip = $tmp['IP']; $port[] = $tmp['PublicPort']; } sort($port); $ip = $nat ? $host : ($ip ?: $DockerClient->myIP($ct['Name']) ?: $host); $list['Port'] = "$ip : ".(implode(' ',array_unique($port)) ?: '???')." -- {$ct['NetworkMode']}"; $ports[] = $list; } return $ports; } # ██████╗ ██████╗ ██████╗ ███████╗ # ██╔════╝██╔═══██╗██╔══██╗██╔════╝ # ██║ ██║ ██║██║ ██║█████╗ # ██║ ██║ ██║██║ ██║██╔══╝ # ╚██████╗╚██████╔╝██████╔╝███████╗ # ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ########################## ## CREATE CONTAINER ## ########################## if (isset($_POST['contName'])) { $postXML = postToXML($_POST, true); $dry_run = $_POST['dryRun']=='true' ? true : false; $existing = $_POST['existingContainer'] ?? false; $create_paths = $dry_run ? false : true; // Get the command line list($cmd, $Name, $Repository) = xmlToCommand($postXML, $create_paths); readfile("$docroot/plugins/dynamix.docker.manager/log.htm"); @flush(); // Saving the generated configuration file. $userTmplDir = $dockerManPaths['templates-user']; if (!is_dir($userTmplDir)) mkdir($userTmplDir, 0777, true); if ($Name) { $filename = sprintf('%s/my-%s.xml', $userTmplDir, $Name); file_put_contents($filename, $postXML); } // Run dry if ($dry_run) { echo "

XML

"; echo "
".htmlspecialchars($postXML)."
"; echo "

COMMAND:

"; echo "
".htmlspecialchars($cmd)."
"; echo "
"; echo "

"; goto END; } // Will only pull image if it's absent if (!$DockerClient->doesImageExist($Repository)) { // Pull image if (!pullImage($Name, $Repository)) { echo '

'; goto END; } } $startContainer = true; // Remove existing container if ($DockerClient->doesContainerExist($Name)) { // attempt graceful stop of container first $oldContainerInfo = $DockerClient->getContainerDetails($Name); if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) { // attempt graceful stop of container first stopContainer($Name); } // force kill container if still running after 10 seconds removeContainer($Name); } // Remove old container if renamed if ($existing && $DockerClient->doesContainerExist($existing)) { // determine if the container is still running $oldContainerInfo = $DockerClient->getContainerDetails($existing); if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) { // attempt graceful stop of container first stopContainer($existing); } else { // old container was stopped already, ensure newly created container doesn't start up automatically $startContainer = false; } // force kill container if still running after 10 seconds removeContainer($existing,1); // remove old template @unlink("$userTmplDir/my-$existing.xml"); } if ($startContainer) { $cmd = str_replace('/plugins/dynamix.docker.manager/scripts/docker create ', '/plugins/dynamix.docker.manager/scripts/docker run -d ', $cmd); } execCommand($cmd); echo '

'; goto END; } ########################## ## UPDATE CONTAINER ## ########################## if ($_GET['updateContainer']){ readfile("$docroot/plugins/dynamix.docker.manager/log.htm"); @flush(); foreach ($_GET['ct'] as $value) { $tmpl = $DockerTemplates->getUserTemplate(urldecode($value)); if (!$tmpl) { echo ""; @flush(); continue; } $xml = file_get_contents($tmpl); list($cmd, $Name, $Repository) = xmlToCommand($tmpl); $Registry = getXmlVal($xml, "Registry"); $oldImageID = $DockerClient->getImageID($Repository); // Pull image if (!pullImage($Name, $Repository)) continue; $oldContainerInfo = $DockerClient->getContainerDetails($Name); // determine if the container is still running if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) { // since container was already running, put it back it to a running state after update $cmd = str_replace('/plugins/dynamix.docker.manager/scripts/docker create ', '/plugins/dynamix.docker.manager/scripts/docker run -d ', $cmd); // attempt graceful stop of container first stopContainer($Name); } // force kill container if still running after 10 seconds removeContainer($Name); execCommand($cmd); $DockerClient->flushCaches(); $newImageID = $DockerClient->getImageID($Repository); if ($oldImageID && $oldImageID != $newImageID) { // remove old orphan image since it's no longer used by this container removeImage($oldImageID); } } echo '

'; goto END; } ######################### ## REMOVE TEMPLATE ## ######################### if ($_GET['rmTemplate']) { unlink($_GET['rmTemplate']); } ######################### ## LOAD TEMPLATE ## ######################### if ($_GET['xmlTemplate']) { list($xmlType, $xmlTemplate) = explode(':', urldecode($_GET['xmlTemplate'])); if (is_file($xmlTemplate)) { $xml = xmlToVar($xmlTemplate); $templateName = $xml['Name']; if ($xmlType == 'default') { if (!empty($dockercfg['DOCKER_APP_CONFIG_PATH']) && file_exists($dockercfg['DOCKER_APP_CONFIG_PATH'])) { // override /config foreach ($xml['Config'] as &$arrConfig) { if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/config') { $arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg['DOCKER_APP_CONFIG_PATH']).'/'.$xml['Name']; if (empty($arrConfig['Display']) || preg_match("/^Host Path\s\d/", $arrConfig['Name'])) { $arrConfig['Display'] = 'advanced-hide'; } if (empty($arrConfig['Name']) || preg_match("/^Host Path\s\d/", $arrConfig['Name'])) { $arrConfig['Name'] = 'AppData Config Path'; } } } } if (!empty($dockercfg['DOCKER_APP_UNRAID_PATH']) && file_exists($dockercfg['DOCKER_APP_UNRAID_PATH'])) { // override /unraid $boolFound = false; foreach ($xml['Config'] as &$arrConfig) { if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/unraid') { $arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg['DOCKER_APP_UNRAID_PATH']); $arrConfig['Display'] = 'hidden'; $arrConfig['Name'] = 'unRAID Share Path'; $boolFound = true; } } if (!$boolFound) { $xml['Config'][] = [ 'Name' => 'unRAID Share Path', 'Target' => '/unraid', 'Default' => realpath($dockercfg['DOCKER_APP_UNRAID_PATH']), 'Value' => realpath($dockercfg['DOCKER_APP_UNRAID_PATH']), 'Mode' => 'rw', 'Description' => '', 'Type' => 'Path', 'Display' => 'hidden', 'Required' => 'false', 'Mask' => 'false' ]; } } } $xml['Description'] = str_replace(['[', ']'], ['<', '>'], $xml['Overview']); echo ""; } } echo ""; $authoringMode = $dockercfg['DOCKER_AUTHORING_MODE'] == "yes" ? true : false; $authoring = $authoringMode ? 'advanced' : 'noshow'; $disableEdit = $authoringMode ? 'false' : 'true'; $showAdditionalInfo = ''; ?>
doesContainerExist($templateName)): echo "\n"; endif; else:?> > > > > > > > >
Template: "; }?>

Templates are a quicker way to setting up Docker Containers on your unRAID server. There are two types of templates:

Default templates
When valid repositories are added to your Docker Repositories page, they will appear in a section on this drop down for you to choose (master categorized by author, then by application template). After selecting a default template, the page will populate with new information about the application in the Description field, and will typically provide instructions for how to setup the container. Select a default template when it is the first time you are configuring this application.

User-defined templates
Once you've added an application to your system through a Default template, the settings you specified are saved to your USB flash device to make it easy to rebuild your applications in the event an upgrade were to fail or if another issue occurred. To rebuild, simply select the previously loaded application from the User-defined list and all the settings for the container will appear populated from your previous setup. Clicking create will redownload the necessary files for the application and should restore you to a working state. To delete a User-defined template, select it from the list above and click the red X to the right of it.

Name:

Give the container a name or leave it as default.

Overview:
Overview:

A description for the application container. Supports basic HTML mark-up.

Repository:

The repository for the application on the Docker Registry. Format of authorname/appname. Optionally you can add a : after appname and request a specific version for the container image.

Categories:
Support Thread:

Link to a support thread on Lime-Technology's forum.

Project Page:

Link to the project page (eg: www.plex.tv)

Minimum unRAID version:

Minimum unRAID version required to run this container. Enforced by the Apps Tab on Installation. If there is no minimum version of unRaid required to run the container, leave blank. Note that any container using 32 bit binaries should have a minimum version set to 6.4.0

Donation Text:

Text to appear on Donation Links Within The Apps Tab

Donation Image:

Link to the image used for Donation Links Within The Apps Tab

Donation Link:

Link to the donation page. If using donation's, both the image and link must be set

Docker Hub URL:

The path to the container's repository location on the Docker Hub.

Template URL:

This URL is used to keep the template updated.

Icon URL:

Link to the icon image for your application (only displayed on dashboard if Show Dashboard apps under Display Settings is set to Icons).

WebUI:

When you click on an application icon from the Docker Containers page, the WebUI option will link to the path in this field. Use [IP] to identify the IP of your host and [PORT:####] replacing the #'s for your port.

Extra Parameters:

If you wish to append additional commands to your Docker container at run-time, you can specify them here.
For example, if you wish to pin an application to live on a specific CPU core, you can enter "--cpuset=0" in this field. Change 0 to the core # on your system (starting with 0). You can pin multiple cores by separation with a comma or a range of cores by separation with a dash. For all possible Docker run-time commands, see here: https://docs.docker.com/reference/run/

Post Arguments:

If you wish to append additional arguments AFTER the container definition, you can specify them here. The content of this field is container specific.

Network Type:

If the Bridge type is selected, the application’s network access will be restricted to only communicating on the ports specified in the port mappings section. If the Host type is selected, the application will be given access to communicate using any port on the host that isn’t already mapped to another in-use application/service. Generally speaking, it is recommended to leave this setting to its default value as specified per application template.

IMPORTANT NOTE: If adjusting port mappings, do not modify the settings for the Container port as only the Host port can be adjusted.

Privileged:

For containers that require the use of host-device access directly or need full exposure to host capabilities, this option will need to be selected.
For more information, see this link: https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration

Show more settings ...

Show docker allocations ...

Add another Path, Port, Variable or Device