feat: custom nginx configuration for static deployments + fix 404 redirects in nginx conf
This commit is contained in:
@@ -1500,7 +1500,7 @@ class ApplicationsController extends Controller
|
||||
], 404);
|
||||
}
|
||||
$server = $application->destination->server;
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server'];
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
@@ -1512,6 +1512,7 @@ class ApplicationsController extends Controller
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_custom_start_command' => 'string|nullable',
|
||||
'docker_compose_custom_build_command' => 'string|nullable',
|
||||
'custom_nginx_configuration' => 'string|nullable',
|
||||
];
|
||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||
$validator = customApiValidator($request->all(), $validationRules);
|
||||
@@ -1530,6 +1531,25 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($request->has('custom_nginx_configuration')) {
|
||||
if (! isBase64Encoded($request->custom_nginx_configuration)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.',
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
$customNginxConfiguration = base64_decode($request->custom_nginx_configuration);
|
||||
if (mb_detect_encoding($customNginxConfiguration, 'ASCII', true) === false) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.',
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
$return = $this->validateDataApplications($request, $server);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
|
@@ -1988,24 +1988,13 @@ WORKDIR /usr/share/nginx/html/
|
||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||
COPY . .
|
||||
RUN rm -f /usr/share/nginx/html/nginx.conf
|
||||
RUN rm -f /usr/share/nginx/html/Dockerfile
|
||||
RUN rm -f /usr/share/nginx/html/Dockerfile`
|
||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$nginx_config = base64_encode('server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri.html $uri/index.html $uri/ /index.html =404;
|
||||
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
||||
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
||||
} else {
|
||||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}');
|
||||
} else {
|
||||
if ($this->application->build_pack === 'nixpacks') {
|
||||
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
|
||||
@@ -2068,23 +2057,13 @@ WORKDIR /usr/share/nginx/html/
|
||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
loggy($this->application->custom_nginx_configuration);
|
||||
|
||||
$nginx_config = base64_encode('server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri.html $uri/index.html $uri/ /index.html =404;
|
||||
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
||||
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
||||
} else {
|
||||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}');
|
||||
}
|
||||
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
|
@@ -84,6 +84,7 @@ class General extends Component
|
||||
'application.pre_deployment_command_container' => 'nullable',
|
||||
'application.post_deployment_command' => 'nullable',
|
||||
'application.post_deployment_command_container' => 'nullable',
|
||||
'application.custom_nginx_configuration' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
||||
@@ -121,6 +122,7 @@ class General extends Component
|
||||
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
||||
@@ -241,6 +243,13 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedApplicationSettingsIsStatic($value)
|
||||
{
|
||||
if ($value) {
|
||||
$this->generateNginxConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedApplicationBuildPack()
|
||||
{
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
@@ -257,6 +266,7 @@ class General extends Component
|
||||
if ($this->application->build_pack === 'static') {
|
||||
$this->application->ports_exposes = $this->ports_exposes = 80;
|
||||
$this->resetDefaultLabels(false);
|
||||
$this->generateNginxConfiguration();
|
||||
}
|
||||
$this->submit();
|
||||
$this->dispatch('buildPackUpdated');
|
||||
@@ -274,6 +284,13 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function generateNginxConfiguration()
|
||||
{
|
||||
$this->application->custom_nginx_configuration = defaultNginxConfiguration();
|
||||
$this->application->save();
|
||||
$this->dispatch('success', 'Nginx configuration generated.');
|
||||
}
|
||||
|
||||
public function resetDefaultLabels($manualReset = false)
|
||||
{
|
||||
try {
|
||||
|
@@ -98,6 +98,7 @@ use Visus\Cuid2\Cuid2;
|
||||
'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'],
|
||||
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
|
||||
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
|
||||
'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'],
|
||||
]
|
||||
)]
|
||||
|
||||
@@ -114,11 +115,11 @@ class Application extends BaseModel
|
||||
protected static function booted()
|
||||
{
|
||||
static::saving(function ($application) {
|
||||
$payload = [];
|
||||
if ($application->isDirty('fqdn')) {
|
||||
if ($application->fqdn === '') {
|
||||
$application->fqdn = null;
|
||||
}
|
||||
$payload = [];
|
||||
if ($application->isDirty('fqdn')) {
|
||||
$payload['fqdn'] = $application->fqdn;
|
||||
}
|
||||
if ($application->isDirty('install_command')) {
|
||||
@@ -139,6 +140,11 @@ class Application extends BaseModel
|
||||
if ($application->isDirty('status')) {
|
||||
$payload['last_online_at'] = now();
|
||||
}
|
||||
if ($application->isDirty('custom_nginx_configuration')) {
|
||||
if ($application->custom_nginx_configuration === '') {
|
||||
$payload['custom_nginx_configuration'] = null;
|
||||
}
|
||||
}
|
||||
if (count($payload) > 0) {
|
||||
$application->forceFill($payload);
|
||||
}
|
||||
@@ -632,6 +638,14 @@ class Application extends BaseModel
|
||||
);
|
||||
}
|
||||
|
||||
public function customNginxConfiguration(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => base64_encode($value),
|
||||
get: fn ($value) => base64_decode($value),
|
||||
);
|
||||
}
|
||||
|
||||
public function portsExposesArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@@ -862,7 +876,7 @@ class Application extends BaseModel
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect;
|
||||
$newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration);
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
||||
} else {
|
||||
|
@@ -4062,3 +4062,31 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla
|
||||
|
||||
return $rateLimited;
|
||||
}
|
||||
|
||||
function defaultNginxConfiguration(): string
|
||||
{
|
||||
return 'server {
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/index.html =404;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
error_page 404 = @handle_404;
|
||||
|
||||
location @handle_404 {
|
||||
root /usr/share/nginx/html;
|
||||
try_files /404.html @redirect_to_index;
|
||||
internal;
|
||||
}
|
||||
|
||||
location @redirect_to_index {
|
||||
return 302 /;
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
@@ -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('applications', function (Blueprint $table) {
|
||||
$table->longText('custom_nginx_configuration')->nullable()->after('static_image');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('custom_nginx_configuration');
|
||||
});
|
||||
}
|
||||
};
|
@@ -61,8 +61,16 @@
|
||||
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->settings->is_static || $application->build_pack === 'static')
|
||||
<x-forms.textarea id="application.custom_nginx_configuration"
|
||||
placeholder="Empty means default configuration will be used." label="Custom Nginx Configuration"
|
||||
helper="You can add custom Nginx configuration here." />
|
||||
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
|
||||
Configuration</x-forms.button>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
<div class="flex items-end gap-2">
|
||||
|
||||
<x-forms.input placeholder="https://coolify.io" wire:model.blur="application.fqdn" label="Domains"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " />
|
||||
<x-forms.button wire:click="getWildcardDomain">Generate Domain
|
||||
|
Reference in New Issue
Block a user