feat: custom internal container names
fix: add warning if hc fails with dockerfile/dockerimage based deployments
This commit is contained in:
parent
bf2e7ff130
commit
05c937743c
@ -869,7 +869,7 @@ private function rolling_update()
|
|||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
}
|
}
|
||||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
||||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
if (count($this->application->ports_mappings_array) > 0) {
|
||||||
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
||||||
@ -877,6 +877,9 @@ private function rolling_update()
|
|||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
|
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
|
||||||
}
|
}
|
||||||
|
if (isset($this->application->settings->custom_internal_name)) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Custom internal name is set, rolling update is not supported.");
|
||||||
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->application->settings->is_consistent_container_name_enabled = true;
|
$this->application->settings->is_consistent_container_name_enabled = true;
|
||||||
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
|
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
|
||||||
@ -1292,7 +1295,9 @@ private function generate_compose_file()
|
|||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'expose' => $ports,
|
'expose' => $ports,
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network,
|
$this->destination->network => [
|
||||||
|
'aliases' => []
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->application->limits_memory,
|
'mem_limit' => $this->application->limits_memory,
|
||||||
'memswap_limit' => $this->application->limits_memory_swap,
|
'memswap_limit' => $this->application->limits_memory_swap,
|
||||||
@ -1310,6 +1315,9 @@ private function generate_compose_file()
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if (isset($this->application->settings->custom_internal_name)) {
|
||||||
|
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
|
||||||
|
}
|
||||||
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
||||||
@ -1519,95 +1527,8 @@ private function generate_local_persistent_volumes_only_volume_names()
|
|||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private function generate_environment_variables($ports)
|
|
||||||
{
|
|
||||||
$environment_variables = collect();
|
|
||||||
if ($this->pull_request_id === 0) {
|
|
||||||
foreach ($this->application->runtime_environment_variables as $env) {
|
|
||||||
// This is necessary because we have to escape the value of the environment variable
|
|
||||||
// but only if the environment variable is created after 4.0.0-beta.240
|
|
||||||
// when I implemented the escaping feature.
|
|
||||||
|
|
||||||
// Old environment variables are not escaped, because it could break the application
|
|
||||||
// as the application could expect the unescaped value.
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add PORT if not exists, use the first port as default
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
|
|
||||||
$environment_variables->push("PORT={$ports[0]}");
|
|
||||||
}
|
|
||||||
// Add HOST if not exists
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
|
|
||||||
$environment_variables->push("HOST=0.0.0.0");
|
|
||||||
}
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
|
|
||||||
if (!is_null($this->commit)) {
|
|
||||||
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("SOURCE_COMMIT=unknown");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ray($environment_variables->all());
|
|
||||||
return $environment_variables->all();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private function generate_healthcheck_commands()
|
private function generate_healthcheck_commands()
|
||||||
{
|
{
|
||||||
// if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
|
||||||
// // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
|
||||||
// return 'exit 0';
|
|
||||||
// }
|
|
||||||
if (!$this->application->health_check_port) {
|
if (!$this->application->health_check_port) {
|
||||||
$health_check_port = $this->application->ports_exposes_array[0];
|
$health_check_port = $this->application->ports_exposes_array[0];
|
||||||
} else {
|
} else {
|
||||||
@ -1619,12 +1540,12 @@ private function generate_healthcheck_commands()
|
|||||||
if ($this->application->health_check_path) {
|
if ($this->application->health_check_path) {
|
||||||
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
|
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
|
||||||
$generated_healthchecks_commands = [
|
$generated_healthchecks_commands = [
|
||||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null"
|
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || exit 1"
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
|
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
|
||||||
$generated_healthchecks_commands = [
|
$generated_healthchecks_commands = [
|
||||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"
|
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || exit 1"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return implode(' ', $generated_healthchecks_commands);
|
return implode(' ', $generated_healthchecks_commands);
|
||||||
@ -1813,12 +1734,17 @@ private function stop_running_container(bool $force = false)
|
|||||||
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if ($this->application->settings->is_consistent_container_name_enabled) {
|
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
||||||
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
|
$this->application_deployment_queue->addLogEntry("WARNING: Dockerfile or Docker Image based deployment detected. The healthcheck needs a curl or wget command to check the health of the application. Please make sure that it is available in the image or turn off healthcheck on Coolify's UI.");
|
||||||
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::FAILED->value,
|
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||||
|
@ -21,6 +21,7 @@ class Advanced extends Component
|
|||||||
'application.settings.is_gpu_enabled' => 'boolean|required',
|
'application.settings.is_gpu_enabled' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
||||||
|
'application.settings.custom_internal_name' => 'string|nullable',
|
||||||
'application.settings.is_gzip_enabled' => 'boolean|required',
|
'application.settings.is_gzip_enabled' => 'boolean|required',
|
||||||
'application.settings.is_stripprefix_enabled' => 'boolean|required',
|
'application.settings.is_stripprefix_enabled' => 'boolean|required',
|
||||||
'application.settings.gpu_driver' => 'string|required',
|
'application.settings.gpu_driver' => 'string|required',
|
||||||
@ -30,7 +31,8 @@ class Advanced extends Component
|
|||||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||||
'application.settings.connect_to_docker_network' => 'boolean|required',
|
'application.settings.connect_to_docker_network' => 'boolean|required',
|
||||||
];
|
];
|
||||||
public function mount() {
|
public function mount()
|
||||||
|
{
|
||||||
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
|
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
|
||||||
$this->is_gzip_enabled = $this->application->isGzipEnabled();
|
$this->is_gzip_enabled = $this->application->isGzipEnabled();
|
||||||
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
|
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
|
||||||
@ -65,7 +67,8 @@ public function instantSave()
|
|||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
}
|
}
|
||||||
public function submit() {
|
public function submit()
|
||||||
|
{
|
||||||
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
|
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
|
||||||
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
|
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
|
||||||
$this->application->settings->gpu_count = null;
|
$this->application->settings->gpu_count = null;
|
||||||
@ -76,6 +79,16 @@ public function submit() {
|
|||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
|
public function saveCustomName()
|
||||||
|
{
|
||||||
|
if (isset($this->application->settings->custom_internal_name)) {
|
||||||
|
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
||||||
|
} else {
|
||||||
|
$this->application->settings->custom_internal_name = null;
|
||||||
|
}
|
||||||
|
$this->application->settings->save();
|
||||||
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.application.advanced');
|
return view('livewire.project.application.advanced');
|
||||||
|
@ -1920,7 +1920,6 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
|
|||||||
$naked_domain = str($domain)->value();
|
$naked_domain = str($domain)->value();
|
||||||
if ($domains->contains($naked_domain)) {
|
if ($domains->contains($naked_domain)) {
|
||||||
if (data_get($resource, 'uuid')) {
|
if (data_get($resource, 'uuid')) {
|
||||||
ray($resource->uuid, $app->uuid);
|
|
||||||
if ($resource->uuid !== $app->uuid) {
|
if ($resource->uuid !== $app->uuid) {
|
||||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
|
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->string('custom_internal_name')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('custom_internal_name');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -8,7 +8,7 @@
|
|||||||
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
||||||
class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]" x-cloak>
|
class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]" x-cloak>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100/40 hover:dark:bg-coolgray-100/100 lg:p-8 lg:flex-row sm:rounded">
|
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 hover:dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-start h-full pb-0 text-xs lg:items-center lg:flex-row lg:pr-6 lg:space-x-5 dark:text-neutral-300 ">
|
class="flex flex-col items-start h-full pb-0 text-xs lg:items-center lg:flex-row lg:pr-6 lg:space-x-5 dark:text-neutral-300 ">
|
||||||
@if (isset($icon))
|
@if (isset($icon))
|
||||||
|
@ -16,10 +16,6 @@
|
|||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
helper="Your application will be available only on https if your domain starts with https://..."
|
helper="Your application will be available only on https if your domain starts with https://..."
|
||||||
instantSave id="is_force_https_enabled" label="Force Https" />
|
instantSave id="is_force_https_enabled" label="Force Https" />
|
||||||
<x-forms.checkbox
|
|
||||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
|
||||||
instantSave id="application.settings.is_consistent_container_name_enabled"
|
|
||||||
label="Consistent Container Names" />
|
|
||||||
<x-forms.checkbox label="Enable gzip compression"
|
<x-forms.checkbox label="Enable gzip compression"
|
||||||
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
|
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
|
||||||
instantSave id="is_gzip_enabled" />
|
instantSave id="is_gzip_enabled" />
|
||||||
@ -30,6 +26,17 @@
|
|||||||
label="Raw Compose Deployment"
|
label="Raw Compose Deployment"
|
||||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
||||||
@endif
|
@endif
|
||||||
|
<h3>Container Names</h3>
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
|
instantSave id="application.settings.is_consistent_container_name_enabled"
|
||||||
|
label="Consistent Container Names" />
|
||||||
|
<form class="flex items-end gap-2 pl-2" wire:submit.prevent='saveCustomName'>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add a custom internal name for your container. This name will be used in the internal network. <br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
|
instantSave id="application.settings.custom_internal_name" label="Add Custom Internal Name" />
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
<h3>Network</h3>
|
<h3>Network</h3>
|
||||||
<div class="w-96">
|
<div class="w-96">
|
||||||
|
Loading…
Reference in New Issue
Block a user