fix(service-apps): harden updates and docker commands
Escape service application lifecycle command arguments for deploy, restart, stop, and log status checks. Validate API update payloads from JSON/form data before applying allowed fields, preserving explicit boolean and null values. Add coverage for service application API authorization, team isolation, validation, and command escaping.
This commit is contained in:
parent
95a3c453d8
commit
a165e03d06
9 changed files with 356 additions and 233 deletions
|
|
@ -22,35 +22,42 @@ public function handle(ServiceApplication $serviceApplication, bool $pullLatestI
|
|||
$service->isConfigurationChanged(save: true);
|
||||
|
||||
$workdir = $service->workdir();
|
||||
$composeFile = "{$workdir}/docker-compose.yml";
|
||||
$safeWorkdir = escapeshellarg($workdir);
|
||||
$safeComposeFile = escapeshellarg($composeFile);
|
||||
$safeProjectName = escapeshellarg($service->uuid);
|
||||
$safeComposeServiceName = escapeshellarg($composeServiceName);
|
||||
|
||||
$commands = collect([
|
||||
"echo 'Saved configuration files to {$workdir}.'",
|
||||
"touch {$workdir}/.env",
|
||||
'echo '.escapeshellarg("Saved configuration files to {$workdir}."),
|
||||
'touch '.escapeshellarg("{$workdir}/.env"),
|
||||
]);
|
||||
|
||||
if ($pullLatestImages) {
|
||||
$commands->push('echo Pulling image for service.');
|
||||
$commands->push("docker compose --project-directory {$workdir} -f {$workdir}/docker-compose.yml --project-name {$service->uuid} pull {$composeServiceName}");
|
||||
$commands->push("docker compose --project-directory {$safeWorkdir} -f {$safeComposeFile} --project-name {$safeProjectName} pull {$safeComposeServiceName}");
|
||||
}
|
||||
|
||||
if ($service->networks()->count() > 0) {
|
||||
$commands->push('echo Creating Docker network.');
|
||||
$commands->push("docker network inspect {$service->uuid} >/dev/null 2>&1 || docker network create --attachable {$service->uuid}");
|
||||
$commands->push("docker network inspect {$safeProjectName} >/dev/null 2>&1 || docker network create --attachable {$safeProjectName}");
|
||||
}
|
||||
|
||||
$upCommand = "docker compose --project-directory {$workdir} -f {$workdir}/docker-compose.yml --project-name {$service->uuid} up -d --no-deps";
|
||||
$upCommand = "docker compose --project-directory {$safeWorkdir} -f {$safeComposeFile} --project-name {$safeProjectName} up -d --no-deps";
|
||||
if ($forceRebuild) {
|
||||
$upCommand .= ' --build';
|
||||
}
|
||||
$upCommand .= " {$composeServiceName}";
|
||||
$upCommand .= " {$safeComposeServiceName}";
|
||||
$commands->push('echo Starting service container.');
|
||||
$commands->push($upCommand);
|
||||
|
||||
$commands->push("docker network connect {$service->uuid} coolify-proxy >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network connect {$safeProjectName} coolify-proxy >/dev/null 2>&1 || true");
|
||||
|
||||
if (data_get($service, 'connect_to_docker_network')) {
|
||||
$compose = data_get($service, 'docker_compose', []);
|
||||
$network = $service->destination->network;
|
||||
$commands->push("docker network connect --alias {$composeServiceName}-{$service->uuid} {$network} {$composeServiceName}-{$service->uuid} >/dev/null 2>&1 || true");
|
||||
$network = escapeshellarg($service->destination->network);
|
||||
$containerName = escapeshellarg("{$composeServiceName}-{$service->uuid}");
|
||||
$networkAlias = escapeshellarg("{$composeServiceName}-{$service->uuid}");
|
||||
$commands->push("docker network connect --alias {$networkAlias} {$network} {$containerName} >/dev/null 2>&1 || true");
|
||||
}
|
||||
|
||||
return remote_process($commands->toArray(), $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public function handle(ServiceApplication $serviceApplication): void
|
|||
{
|
||||
$service = $serviceApplication->service;
|
||||
$server = $service->destination->server;
|
||||
$containerName = $serviceApplication->name.'-'.$service->uuid;
|
||||
$containerName = escapeshellarg($serviceApplication->name.'-'.$service->uuid);
|
||||
|
||||
instant_remote_process([
|
||||
"docker restart {$containerName}",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public function handle(ServiceApplication $serviceApplication): void
|
|||
{
|
||||
$service = $serviceApplication->service;
|
||||
$server = $service->destination->server;
|
||||
$containerName = $serviceApplication->name.'-'.$service->uuid;
|
||||
$containerName = escapeshellarg($serviceApplication->name.'-'.$service->uuid);
|
||||
|
||||
instant_remote_process([
|
||||
"docker stop {$containerName}",
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
class UpdateServiceApplicationFromApi
|
||||
{
|
||||
public function execute(ServiceApplication $serviceApplication, Request $request, string $teamId): ?JsonResponse
|
||||
public function execute(ServiceApplication $serviceApplication, Request $request, string $teamId, array $payload): ?JsonResponse
|
||||
{
|
||||
$forceDomainOverride = $request->boolean('force_domain_override');
|
||||
|
||||
if ($request->has('url')) {
|
||||
$urlRaw = $request->input('url');
|
||||
if (array_key_exists('url', $payload)) {
|
||||
$urlRaw = $payload['url'];
|
||||
if ($urlRaw !== null && ! is_string($urlRaw)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
|
|
@ -59,38 +59,38 @@ public function execute(ServiceApplication $serviceApplication, Request $request
|
|||
$serviceApplication->fqdn = $parsed['normalized'];
|
||||
}
|
||||
|
||||
if ($request->has('human_name')) {
|
||||
$serviceApplication->human_name = $request->input('human_name');
|
||||
if (array_key_exists('human_name', $payload)) {
|
||||
$serviceApplication->human_name = $payload['human_name'];
|
||||
}
|
||||
|
||||
if ($request->has('description')) {
|
||||
$serviceApplication->description = $request->input('description');
|
||||
if (array_key_exists('description', $payload)) {
|
||||
$serviceApplication->description = $payload['description'];
|
||||
}
|
||||
|
||||
if ($request->has('image')) {
|
||||
$serviceApplication->image = $request->input('image');
|
||||
if (array_key_exists('image', $payload)) {
|
||||
$serviceApplication->image = $payload['image'];
|
||||
}
|
||||
|
||||
if ($request->has('exclude_from_status')) {
|
||||
$serviceApplication->exclude_from_status = $request->boolean('exclude_from_status');
|
||||
if (array_key_exists('exclude_from_status', $payload)) {
|
||||
$serviceApplication->exclude_from_status = filter_var($payload['exclude_from_status'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
if ($request->has('is_gzip_enabled')) {
|
||||
$serviceApplication->is_gzip_enabled = $request->boolean('is_gzip_enabled');
|
||||
if (array_key_exists('is_gzip_enabled', $payload)) {
|
||||
$serviceApplication->is_gzip_enabled = filter_var($payload['is_gzip_enabled'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
if ($request->has('is_stripprefix_enabled')) {
|
||||
$serviceApplication->is_stripprefix_enabled = $request->boolean('is_stripprefix_enabled');
|
||||
if (array_key_exists('is_stripprefix_enabled', $payload)) {
|
||||
$serviceApplication->is_stripprefix_enabled = filter_var($payload['is_stripprefix_enabled'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
if ($request->has('is_log_drain_enabled')) {
|
||||
$enabled = $request->boolean('is_log_drain_enabled');
|
||||
if (array_key_exists('is_log_drain_enabled', $payload)) {
|
||||
$enabled = filter_var($payload['is_log_drain_enabled'], FILTER_VALIDATE_BOOLEAN);
|
||||
$server = $serviceApplication->service->destination->server;
|
||||
if ($enabled && ! $server->isLogDrainEnabled()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'is_log_drain_enabled' => 'Log drain is not enabled on the server for this service.',
|
||||
'is_log_drain_enabled' => ['Log drain is not enabled on the server for this service.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,6 +306,11 @@ public function update(Request $request, UpdateServiceApplicationFromApi $update
|
|||
|
||||
$this->authorize('update', $serviceApplication);
|
||||
|
||||
$payload = $request->json()->all();
|
||||
if (empty($payload)) {
|
||||
$payload = $request->request->all();
|
||||
}
|
||||
|
||||
$allowedFields = [
|
||||
'url',
|
||||
'human_name',
|
||||
|
|
@ -328,9 +333,9 @@ public function update(Request $request, UpdateServiceApplicationFromApi $update
|
|||
'is_stripprefix_enabled' => 'sometimes|boolean',
|
||||
];
|
||||
|
||||
$validator = Validator::make($request->all(), $validationRules);
|
||||
$validator = Validator::make($payload, $validationRules);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
$extraFields = array_diff(array_keys($payload), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
$errors = $validator->errors();
|
||||
foreach ($extraFields as $field) {
|
||||
|
|
@ -343,7 +348,7 @@ public function update(Request $request, UpdateServiceApplicationFromApi $update
|
|||
], 422);
|
||||
}
|
||||
|
||||
$response = $updateServiceApplicationFromApi->execute($serviceApplication, $request, $teamId);
|
||||
$response = $updateServiceApplicationFromApi->execute($serviceApplication, $request, $teamId, $payload);
|
||||
if ($response instanceof JsonResponse) {
|
||||
return $response;
|
||||
}
|
||||
|
|
@ -450,8 +455,9 @@ public function logs_by_uuid(Request $request): JsonResponse
|
|||
}
|
||||
|
||||
$containerName = $serviceApplication->name.'-'.$serviceApplication->service->uuid;
|
||||
$safeContainerName = escapeshellarg($containerName);
|
||||
|
||||
$status = getContainerStatus($server, $containerName);
|
||||
$status = getContainerStatus($server, $safeContainerName);
|
||||
if ($status !== 'running') {
|
||||
return response()->json([
|
||||
'message' => 'Service application container is not running.',
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/actualbudget.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "5006"
|
||||
},
|
||||
"affine": {
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/alexandrie.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T21:03:32+01:00",
|
||||
"template_last_updated_at": "2026-04-05T13:36:24+02:00",
|
||||
"port": "8200"
|
||||
},
|
||||
"anythingllm": {
|
||||
|
|
@ -175,7 +175,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/argilla.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "6900"
|
||||
},
|
||||
"audiobookshelf": {
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/audiobookshelf.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T09:09:36+02:00",
|
||||
"port": "80"
|
||||
},
|
||||
"authentik": {
|
||||
|
|
@ -231,7 +231,7 @@
|
|||
"category": "database",
|
||||
"logo": "svgs/autobase.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-19T18:13:51+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"babybuddy": {
|
||||
|
|
@ -280,7 +280,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/beszel.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-21T23:17:23+05:30"
|
||||
"template_last_updated_at": "2026-04-24T02:22:08+05:30"
|
||||
},
|
||||
"beszel": {
|
||||
"documentation": "https://github.com/henrygd/beszel?tab=readme-ov-file#getting-started?utm_source=coolify.io",
|
||||
|
|
@ -296,7 +296,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/beszel.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-21T23:17:23+05:30",
|
||||
"template_last_updated_at": "2026-04-24T02:21:33+05:30",
|
||||
"port": "8090"
|
||||
},
|
||||
"bitcoin-core": {
|
||||
|
|
@ -326,7 +326,7 @@
|
|||
"category": "backend",
|
||||
"logo": "svgs/bluesky.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-03T22:12:21+01:00",
|
||||
"template_last_updated_at": "2026-05-09T19:19:48+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"bookstack": {
|
||||
|
|
@ -377,7 +377,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/budge.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00"
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00"
|
||||
},
|
||||
"budibase": {
|
||||
"documentation": "https://docs.budibase.com/docs/docker-compose?utm_source=coolify.io",
|
||||
|
|
@ -438,7 +438,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/calibre-web-automated-with-downloader.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-08T21:32:32+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8083"
|
||||
},
|
||||
"calibre-web": {
|
||||
|
|
@ -475,7 +475,7 @@
|
|||
"category": "security",
|
||||
"logo": "svgs/cap-captcha.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-04-23T01:07:14+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"cap": {
|
||||
|
|
@ -493,7 +493,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/cap.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-14T23:42:45+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "5679"
|
||||
},
|
||||
"castopod": {
|
||||
|
|
@ -552,7 +552,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/chaskiq.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"chatwoot": {
|
||||
|
|
@ -573,7 +573,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/chatwoot.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-27T09:31:29-03:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"checkmate": {
|
||||
|
|
@ -605,7 +605,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/chibisafe.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-12T22:51:57+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"chroma": {
|
||||
|
|
@ -704,7 +704,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/cloudflare-ddns.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null
|
||||
"template_last_updated_at": "2026-05-18T15:42:31+10:00"
|
||||
},
|
||||
"cloudflared": {
|
||||
"documentation": "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/?utm_source=coolify.io",
|
||||
|
|
@ -803,7 +803,7 @@
|
|||
"category": "backend",
|
||||
"logo": "svgs/convex.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-10T14:16:14+01:00",
|
||||
"template_last_updated_at": "2026-06-12T10:45:52+02:00",
|
||||
"port": "6791"
|
||||
},
|
||||
"cryptgeon": {
|
||||
|
|
@ -903,7 +903,7 @@
|
|||
"category": "cms",
|
||||
"logo": "svgs/directus.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-03T19:25:51+05:30",
|
||||
"port": "8055"
|
||||
},
|
||||
"directus": {
|
||||
|
|
@ -919,7 +919,7 @@
|
|||
"category": "cms",
|
||||
"logo": "svgs/directus.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-03T19:26:06+05:30",
|
||||
"port": "8055"
|
||||
},
|
||||
"diun": {
|
||||
|
|
@ -968,7 +968,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/docmost.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-09-27T04:00:56+02:00",
|
||||
"template_last_updated_at": "2026-05-15T13:36:02+02:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"documenso": {
|
||||
|
|
@ -1044,7 +1044,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/dolibarr.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-18T10:27:24+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"dozzle-with-auth": {
|
||||
|
|
@ -1093,7 +1093,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/drizzle.jpeg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "4983"
|
||||
},
|
||||
"drupal-with-postgresql": {
|
||||
|
|
@ -1161,7 +1161,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/elasticsearch.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T21:34:40+05:30",
|
||||
"template_last_updated_at": "2026-04-09T00:43:59-05:00",
|
||||
"port": "5601"
|
||||
},
|
||||
"elasticsearch": {
|
||||
|
|
@ -1248,7 +1248,7 @@
|
|||
"category": "Networking",
|
||||
"logo": "svgs/emqx-enterprise.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-04-27T09:25:48+03:00",
|
||||
"port": "18083"
|
||||
},
|
||||
"ente-photos-with-s3": {
|
||||
|
|
@ -1322,7 +1322,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/espocrm.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-03-16T18:23:47+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"evolution-api": {
|
||||
|
|
@ -1436,7 +1436,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/firefly.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"firefox": {
|
||||
|
|
@ -1625,7 +1625,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/formbricks.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T21:12:57+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"foundryvtt": {
|
||||
|
|
@ -1642,7 +1642,7 @@
|
|||
"category": "games",
|
||||
"logo": "svgs/foundryvtt.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "30000"
|
||||
},
|
||||
"freescout": {
|
||||
|
|
@ -1658,7 +1658,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/freescout.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss-with-mariadb": {
|
||||
|
|
@ -1672,7 +1672,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss-with-mysql": {
|
||||
|
|
@ -1686,7 +1686,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss-with-postgresql": {
|
||||
|
|
@ -1700,7 +1700,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-04T17:41:25+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss": {
|
||||
|
|
@ -1714,7 +1714,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"garage": {
|
||||
|
|
@ -1732,7 +1732,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/garage.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-10T12:57:34+01:00",
|
||||
"template_last_updated_at": "2026-05-31T23:57:46+02:00",
|
||||
"port": "3900"
|
||||
},
|
||||
"getoutline": {
|
||||
|
|
@ -1746,7 +1746,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/getoutline.jpeg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-16T14:12:15+07:00",
|
||||
"template_last_updated_at": "2026-05-09T19:19:29+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"ghost": {
|
||||
|
|
@ -1779,7 +1779,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/gitea.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null
|
||||
"template_last_updated_at": "2026-06-06T00:11:24+02:00"
|
||||
},
|
||||
"gitea-with-mariadb": {
|
||||
"documentation": "https://docs.gitea.com?utm_source=coolify.io",
|
||||
|
|
@ -1910,7 +1910,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/glance.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-18T19:18:07+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"glances": {
|
||||
|
|
@ -2000,7 +2000,7 @@
|
|||
"category": "messaging",
|
||||
"logo": "svgs/gotify.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-08T11:43:21+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"gowa": {
|
||||
|
|
@ -2017,7 +2017,7 @@
|
|||
"category": "messaging",
|
||||
"logo": "svgs/gowa.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"grafana-with-postgresql": {
|
||||
|
|
@ -2080,7 +2080,7 @@
|
|||
"category": null,
|
||||
"logo": "svgs/grimmory.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-04-03T18:59:26+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"grist": {
|
||||
|
|
@ -2114,7 +2114,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/grocy.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00"
|
||||
"template_last_updated_at": "2026-05-12T09:10:44+02:00"
|
||||
},
|
||||
"hatchet": {
|
||||
"documentation": "https://docs.hatchet.run/self-hosting/docker-compose?utm_source=coolify.io",
|
||||
|
|
@ -2129,7 +2129,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/hatchet.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-16T22:27:56+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"healthchecks": {
|
||||
|
|
@ -2148,7 +2148,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/healthchecks.webp",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-05-25T13:06:59-04:00",
|
||||
"port": "80000"
|
||||
},
|
||||
"heimdall": {
|
||||
|
|
@ -2183,7 +2183,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/hermes-agent.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-05-29T12:14:17+05:30",
|
||||
"port": "8787"
|
||||
},
|
||||
"heyform": {
|
||||
|
|
@ -2218,7 +2218,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/homarr.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-07T12:19:45+02:00",
|
||||
"template_last_updated_at": "2026-05-09T19:30:07+05:30",
|
||||
"port": "7575"
|
||||
},
|
||||
"home-assistant": {
|
||||
|
|
@ -2253,7 +2253,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/homebox.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "7745"
|
||||
},
|
||||
"homepage": {
|
||||
|
|
@ -2361,7 +2361,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/inngest.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-07-02T13:25:47+02:00",
|
||||
"port": "8288"
|
||||
},
|
||||
"invoice-ninja": {
|
||||
|
|
@ -2378,7 +2378,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/invoiceninja.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "9000"
|
||||
},
|
||||
"it-tools": {
|
||||
|
|
@ -2410,7 +2410,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/jellyfin.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T08:58:05+02:00",
|
||||
"port": "8096"
|
||||
},
|
||||
"jenkins": {
|
||||
|
|
@ -2443,7 +2443,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/jitsi.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-24T09:40:01+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"joomla-with-mariadb": {
|
||||
|
|
@ -2672,7 +2672,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/langfuse.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-03-28T16:21:57+01:00",
|
||||
"template_last_updated_at": "2026-04-23T18:08:40+02:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"leantime": {
|
||||
|
|
@ -2713,7 +2713,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/librechat.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-03T18:29:44+05:30",
|
||||
"port": "3080"
|
||||
},
|
||||
"libreoffice": {
|
||||
|
|
@ -2881,7 +2881,7 @@
|
|||
"category": "auth",
|
||||
"logo": "svgs/logto_dark.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00"
|
||||
"template_last_updated_at": "2026-04-01T13:19:47Z"
|
||||
},
|
||||
"lowcoder": {
|
||||
"documentation": "https://docs.lowcoder.cloud/?utm_source=coolify.io",
|
||||
|
|
@ -3005,7 +3005,7 @@
|
|||
"category": "messaging",
|
||||
"logo": "svgs/mattermost.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-09-04T16:02:48+03:30",
|
||||
"template_last_updated_at": "2026-04-06T13:57:40-05:00",
|
||||
"port": "8065"
|
||||
},
|
||||
"mealie": {
|
||||
|
|
@ -3021,7 +3021,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/mealie.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T09:22:15+02:00",
|
||||
"port": "9000"
|
||||
},
|
||||
"mediawiki": {
|
||||
|
|
@ -3149,7 +3149,7 @@
|
|||
"category": "games",
|
||||
"logo": "svgs/minecraft.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-01T15:06:17-05:00",
|
||||
"port": "25565"
|
||||
},
|
||||
"miniflux": {
|
||||
|
|
@ -3166,7 +3166,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/miniflux.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"mixpost": {
|
||||
|
|
@ -3242,7 +3242,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/n8n.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-03-08T01:08:18-05:00",
|
||||
"template_last_updated_at": "2026-03-30T20:40:07+02:00",
|
||||
"port": "5678"
|
||||
},
|
||||
"n8n-with-postgresql": {
|
||||
|
|
@ -3261,7 +3261,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/n8n.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-27T22:41:52-05:00",
|
||||
"template_last_updated_at": "2026-03-30T20:40:07+02:00",
|
||||
"port": "5678"
|
||||
},
|
||||
"n8n": {
|
||||
|
|
@ -3280,7 +3280,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/n8n.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-27T22:41:52-05:00",
|
||||
"template_last_updated_at": "2026-03-30T20:40:07+02:00",
|
||||
"port": "5678"
|
||||
},
|
||||
"navidrome": {
|
||||
|
|
@ -3328,7 +3328,7 @@
|
|||
"category": "vpn",
|
||||
"logo": "svgs/netbird.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-16T10:33:10+05:30"
|
||||
"template_last_updated_at": "2026-04-09T00:00:38+05:30"
|
||||
},
|
||||
"newapi": {
|
||||
"documentation": "https://docs.newapi.pro/en/getting-started/?utm_source=coolify.io",
|
||||
|
|
@ -3360,7 +3360,7 @@
|
|||
"category": "proxy",
|
||||
"logo": "svgs/pangolin-logo.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-12T11:59:36-03:00"
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00"
|
||||
},
|
||||
"next-image-transformation": {
|
||||
"documentation": "https://github.com/coollabsio/next-image-transformation?utm_source=coolify.io",
|
||||
|
|
@ -3392,7 +3392,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nextcloud-with-mysql": {
|
||||
|
|
@ -3409,7 +3409,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nextcloud-with-postgres": {
|
||||
|
|
@ -3426,7 +3426,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nextcloud": {
|
||||
|
|
@ -3443,7 +3443,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nexus-arm": {
|
||||
|
|
@ -3554,7 +3554,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/nocodb.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"nodebb": {
|
||||
|
|
@ -3621,7 +3621,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/odoo.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8069"
|
||||
},
|
||||
"ollama-with-open-webui": {
|
||||
|
|
@ -3712,7 +3712,7 @@
|
|||
"category": "email",
|
||||
"logo": "svgs/openarchiver.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-12T23:41:36+01:00",
|
||||
"template_last_updated_at": "2026-05-09T19:35:15+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"open-webui": {
|
||||
|
|
@ -3770,7 +3770,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/openobserve.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-05-19T16:40:18+05:30",
|
||||
"port": "5080"
|
||||
},
|
||||
"openpanel": {
|
||||
|
|
@ -3789,7 +3789,7 @@
|
|||
"category": "analytics",
|
||||
"logo": "svgs/openpanel.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T20:48:57+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"opnform": {
|
||||
|
|
@ -3810,7 +3810,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svg/opnform.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-24T10:13:08+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"orangehrm": {
|
||||
|
|
@ -3828,7 +3828,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/orangehrm.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"organizr": {
|
||||
|
|
@ -3857,7 +3857,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/osticket.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"overseerr": {
|
||||
|
|
@ -3891,7 +3891,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/owncloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-06-02T12:12:42+03:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"pairdrop": {
|
||||
|
|
@ -3918,7 +3918,7 @@
|
|||
"category": "games",
|
||||
"logo": "svgs/default.webp",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-15T21:55:50+01:00"
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00"
|
||||
},
|
||||
"paperless": {
|
||||
"documentation": "https://docs.paperless-ngx.com/configuration/?utm_source=coolify.io",
|
||||
|
|
@ -3959,7 +3959,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/paymenter.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-23T12:08:21+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"penpot-with-s3": {
|
||||
|
|
@ -4075,7 +4075,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/plane.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-24T02:34:35+05:30",
|
||||
"template_last_updated_at": "2026-04-24T00:12:17+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"plex": {
|
||||
|
|
@ -4108,7 +4108,7 @@
|
|||
"category": "email",
|
||||
"logo": "svgs/plunk.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"pocket-id-with-postgresql": {
|
||||
|
|
@ -4265,7 +4265,7 @@
|
|||
"category": "proxy",
|
||||
"logo": "svgs/hoppscotch.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-13T19:24:05+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "9159"
|
||||
},
|
||||
"qbittorrent": {
|
||||
|
|
@ -4351,7 +4351,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/rallly.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-03-19T16:11:50+01:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"reactive-resume": {
|
||||
|
|
@ -4428,7 +4428,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/redmine.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-16T22:27:56+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"rivet-engine": {
|
||||
|
|
@ -4446,7 +4446,7 @@
|
|||
"category": "development",
|
||||
"logo": "svgs/rivet.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-22T16:07:42+02:00",
|
||||
"template_last_updated_at": "2026-04-01T19:58:30+02:00",
|
||||
"port": "6420"
|
||||
},
|
||||
"rocketchat": {
|
||||
|
|
@ -4502,7 +4502,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/ryot.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T08:34:57+02:00",
|
||||
"port": "8000"
|
||||
},
|
||||
"satisfactory": {
|
||||
|
|
@ -4621,7 +4621,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/sftpgo.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-12T23:44:06+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"shlink": {
|
||||
|
|
@ -4660,7 +4660,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/signoz.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-11T17:51:10+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"silverbullet": {
|
||||
|
|
@ -4675,7 +4675,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/silverbullet.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-05T15:42:12+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"siyuan": {
|
||||
|
|
@ -4759,7 +4759,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/soketi-app-manager.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-16T22:27:17+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"soketi": {
|
||||
|
|
@ -4898,7 +4898,7 @@
|
|||
"category": "backend",
|
||||
"logo": "svgs/supabase.svg",
|
||||
"minversion": "4.0.0-beta.228",
|
||||
"template_last_updated_at": "2026-01-20T19:10:07+01:00",
|
||||
"template_last_updated_at": "2026-04-05T20:21:11+02:00",
|
||||
"port": "8000"
|
||||
},
|
||||
"superset-with-postgresql": {
|
||||
|
|
@ -5176,7 +5176,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/twenty.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-08T18:51:23+01:00",
|
||||
"template_last_updated_at": "2026-04-16T23:18:19+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"typesense": {
|
||||
|
|
@ -5224,7 +5224,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/unleash.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-04T19:45:35+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "4242"
|
||||
},
|
||||
"unleash-without-database": {
|
||||
|
|
@ -5241,7 +5241,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/unleash.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "4242"
|
||||
},
|
||||
"unstructured": {
|
||||
|
|
@ -5339,7 +5339,7 @@
|
|||
"category": "email",
|
||||
"logo": "svgs/usesend.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-07T17:32:23+11:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"vaultwarden": {
|
||||
|
|
@ -5774,7 +5774,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/cells.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-13T18:47:54+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/actualbudget.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "5006"
|
||||
},
|
||||
"affine": {
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/alexandrie.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T21:03:32+01:00",
|
||||
"template_last_updated_at": "2026-04-05T13:36:24+02:00",
|
||||
"port": "8200"
|
||||
},
|
||||
"anythingllm": {
|
||||
|
|
@ -175,7 +175,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/argilla.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "6900"
|
||||
},
|
||||
"audiobookshelf": {
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/audiobookshelf.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T09:09:36+02:00",
|
||||
"port": "80"
|
||||
},
|
||||
"authentik": {
|
||||
|
|
@ -231,7 +231,7 @@
|
|||
"category": "database",
|
||||
"logo": "svgs/autobase.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-19T18:13:51+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"babybuddy": {
|
||||
|
|
@ -280,7 +280,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/beszel.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-21T23:17:23+05:30"
|
||||
"template_last_updated_at": "2026-04-24T02:22:08+05:30"
|
||||
},
|
||||
"beszel": {
|
||||
"documentation": "https://github.com/henrygd/beszel?tab=readme-ov-file#getting-started?utm_source=coolify.io",
|
||||
|
|
@ -296,7 +296,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/beszel.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-21T23:17:23+05:30",
|
||||
"template_last_updated_at": "2026-04-24T02:21:33+05:30",
|
||||
"port": "8090"
|
||||
},
|
||||
"bitcoin-core": {
|
||||
|
|
@ -326,7 +326,7 @@
|
|||
"category": "backend",
|
||||
"logo": "svgs/bluesky.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-03T22:12:21+01:00",
|
||||
"template_last_updated_at": "2026-05-09T19:19:48+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"bookstack": {
|
||||
|
|
@ -377,7 +377,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/budge.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00"
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00"
|
||||
},
|
||||
"budibase": {
|
||||
"documentation": "https://docs.budibase.com/docs/docker-compose?utm_source=coolify.io",
|
||||
|
|
@ -438,7 +438,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/calibre-web-automated-with-downloader.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-08T21:32:32+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8083"
|
||||
},
|
||||
"calibre-web": {
|
||||
|
|
@ -475,7 +475,7 @@
|
|||
"category": "security",
|
||||
"logo": "svgs/cap-captcha.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-04-23T01:07:14+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"cap": {
|
||||
|
|
@ -493,7 +493,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/cap.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-14T23:42:45+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "5679"
|
||||
},
|
||||
"castopod": {
|
||||
|
|
@ -552,7 +552,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/chaskiq.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"chatwoot": {
|
||||
|
|
@ -573,7 +573,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/chatwoot.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-27T09:31:29-03:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"checkmate": {
|
||||
|
|
@ -605,7 +605,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/chibisafe.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-12T22:51:57+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"chroma": {
|
||||
|
|
@ -704,7 +704,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/cloudflare-ddns.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null
|
||||
"template_last_updated_at": "2026-05-18T15:42:31+10:00"
|
||||
},
|
||||
"cloudflared": {
|
||||
"documentation": "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/?utm_source=coolify.io",
|
||||
|
|
@ -803,7 +803,7 @@
|
|||
"category": "backend",
|
||||
"logo": "svgs/convex.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-10T14:16:14+01:00",
|
||||
"template_last_updated_at": "2026-06-12T10:45:52+02:00",
|
||||
"port": "6791"
|
||||
},
|
||||
"cryptgeon": {
|
||||
|
|
@ -903,7 +903,7 @@
|
|||
"category": "cms",
|
||||
"logo": "svgs/directus.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-03T19:25:51+05:30",
|
||||
"port": "8055"
|
||||
},
|
||||
"directus": {
|
||||
|
|
@ -919,7 +919,7 @@
|
|||
"category": "cms",
|
||||
"logo": "svgs/directus.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-03T19:26:06+05:30",
|
||||
"port": "8055"
|
||||
},
|
||||
"diun": {
|
||||
|
|
@ -968,7 +968,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/docmost.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-09-27T04:00:56+02:00",
|
||||
"template_last_updated_at": "2026-05-15T13:36:02+02:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"documenso": {
|
||||
|
|
@ -1044,7 +1044,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/dolibarr.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-18T10:27:24+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"dozzle-with-auth": {
|
||||
|
|
@ -1093,7 +1093,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/drizzle.jpeg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "4983"
|
||||
},
|
||||
"drupal-with-postgresql": {
|
||||
|
|
@ -1161,7 +1161,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/elasticsearch.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T21:34:40+05:30",
|
||||
"template_last_updated_at": "2026-04-09T00:43:59-05:00",
|
||||
"port": "5601"
|
||||
},
|
||||
"elasticsearch": {
|
||||
|
|
@ -1248,7 +1248,7 @@
|
|||
"category": "Networking",
|
||||
"logo": "svgs/emqx-enterprise.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-04-27T09:25:48+03:00",
|
||||
"port": "18083"
|
||||
},
|
||||
"ente-photos-with-s3": {
|
||||
|
|
@ -1322,7 +1322,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/espocrm.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-03-16T18:23:47+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"evolution-api": {
|
||||
|
|
@ -1436,7 +1436,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/firefly.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"firefox": {
|
||||
|
|
@ -1625,7 +1625,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/formbricks.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T21:12:57+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"foundryvtt": {
|
||||
|
|
@ -1642,7 +1642,7 @@
|
|||
"category": "games",
|
||||
"logo": "svgs/foundryvtt.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "30000"
|
||||
},
|
||||
"freescout": {
|
||||
|
|
@ -1658,7 +1658,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/freescout.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss-with-mariadb": {
|
||||
|
|
@ -1672,7 +1672,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss-with-mysql": {
|
||||
|
|
@ -1686,7 +1686,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss-with-postgresql": {
|
||||
|
|
@ -1700,7 +1700,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-04T17:41:25+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"freshrss": {
|
||||
|
|
@ -1714,7 +1714,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/freshrss.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"garage": {
|
||||
|
|
@ -1732,7 +1732,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/garage.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-10T12:57:34+01:00",
|
||||
"template_last_updated_at": "2026-05-31T23:57:46+02:00",
|
||||
"port": "3900"
|
||||
},
|
||||
"getoutline": {
|
||||
|
|
@ -1746,7 +1746,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/getoutline.jpeg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-16T14:12:15+07:00",
|
||||
"template_last_updated_at": "2026-05-09T19:19:29+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"ghost": {
|
||||
|
|
@ -1779,7 +1779,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/gitea.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null
|
||||
"template_last_updated_at": "2026-06-06T00:11:24+02:00"
|
||||
},
|
||||
"gitea-with-mariadb": {
|
||||
"documentation": "https://docs.gitea.com?utm_source=coolify.io",
|
||||
|
|
@ -1910,7 +1910,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/glance.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-18T19:18:07+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"glances": {
|
||||
|
|
@ -2000,7 +2000,7 @@
|
|||
"category": "messaging",
|
||||
"logo": "svgs/gotify.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-08T11:43:21+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"gowa": {
|
||||
|
|
@ -2017,7 +2017,7 @@
|
|||
"category": "messaging",
|
||||
"logo": "svgs/gowa.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"grafana-with-postgresql": {
|
||||
|
|
@ -2080,7 +2080,7 @@
|
|||
"category": null,
|
||||
"logo": "svgs/grimmory.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-04-03T18:59:26+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"grist": {
|
||||
|
|
@ -2114,7 +2114,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/grocy.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00"
|
||||
"template_last_updated_at": "2026-05-12T09:10:44+02:00"
|
||||
},
|
||||
"hatchet": {
|
||||
"documentation": "https://docs.hatchet.run/self-hosting/docker-compose?utm_source=coolify.io",
|
||||
|
|
@ -2129,7 +2129,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/hatchet.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-16T22:27:56+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"healthchecks": {
|
||||
|
|
@ -2148,7 +2148,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/healthchecks.webp",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-05-25T13:06:59-04:00",
|
||||
"port": "80000"
|
||||
},
|
||||
"heimdall": {
|
||||
|
|
@ -2183,7 +2183,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/hermes-agent.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-05-29T12:14:17+05:30",
|
||||
"port": "8787"
|
||||
},
|
||||
"heyform": {
|
||||
|
|
@ -2218,7 +2218,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/homarr.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-07T12:19:45+02:00",
|
||||
"template_last_updated_at": "2026-05-09T19:30:07+05:30",
|
||||
"port": "7575"
|
||||
},
|
||||
"home-assistant": {
|
||||
|
|
@ -2253,7 +2253,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/homebox.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "7745"
|
||||
},
|
||||
"homepage": {
|
||||
|
|
@ -2361,7 +2361,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/inngest.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-07-02T13:25:47+02:00",
|
||||
"port": "8288"
|
||||
},
|
||||
"invoice-ninja": {
|
||||
|
|
@ -2378,7 +2378,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/invoiceninja.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "9000"
|
||||
},
|
||||
"it-tools": {
|
||||
|
|
@ -2410,7 +2410,7 @@
|
|||
"category": "media",
|
||||
"logo": "svgs/jellyfin.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T08:58:05+02:00",
|
||||
"port": "8096"
|
||||
},
|
||||
"jenkins": {
|
||||
|
|
@ -2443,7 +2443,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/jitsi.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-24T09:40:01+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"joomla-with-mariadb": {
|
||||
|
|
@ -2672,7 +2672,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/langfuse.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-03-28T16:21:57+01:00",
|
||||
"template_last_updated_at": "2026-04-23T18:08:40+02:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"leantime": {
|
||||
|
|
@ -2713,7 +2713,7 @@
|
|||
"category": "ai",
|
||||
"logo": "svgs/librechat.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-03T18:29:44+05:30",
|
||||
"port": "3080"
|
||||
},
|
||||
"libreoffice": {
|
||||
|
|
@ -2881,7 +2881,7 @@
|
|||
"category": "auth",
|
||||
"logo": "svgs/logto_dark.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00"
|
||||
"template_last_updated_at": "2026-04-01T13:19:47Z"
|
||||
},
|
||||
"lowcoder": {
|
||||
"documentation": "https://docs.lowcoder.cloud/?utm_source=coolify.io",
|
||||
|
|
@ -3005,7 +3005,7 @@
|
|||
"category": "messaging",
|
||||
"logo": "svgs/mattermost.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-09-04T16:02:48+03:30",
|
||||
"template_last_updated_at": "2026-04-06T13:57:40-05:00",
|
||||
"port": "8065"
|
||||
},
|
||||
"mealie": {
|
||||
|
|
@ -3021,7 +3021,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/mealie.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T09:22:15+02:00",
|
||||
"port": "9000"
|
||||
},
|
||||
"mediawiki": {
|
||||
|
|
@ -3149,7 +3149,7 @@
|
|||
"category": "games",
|
||||
"logo": "svgs/minecraft.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-01T15:06:17-05:00",
|
||||
"port": "25565"
|
||||
},
|
||||
"miniflux": {
|
||||
|
|
@ -3166,7 +3166,7 @@
|
|||
"category": "RSS",
|
||||
"logo": "svgs/miniflux.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"mixpost": {
|
||||
|
|
@ -3242,7 +3242,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/n8n.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-03-08T01:08:18-05:00",
|
||||
"template_last_updated_at": "2026-03-30T20:40:07+02:00",
|
||||
"port": "5678"
|
||||
},
|
||||
"n8n-with-postgresql": {
|
||||
|
|
@ -3261,7 +3261,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/n8n.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-27T22:41:52-05:00",
|
||||
"template_last_updated_at": "2026-03-30T20:40:07+02:00",
|
||||
"port": "5678"
|
||||
},
|
||||
"n8n": {
|
||||
|
|
@ -3280,7 +3280,7 @@
|
|||
"category": "automation",
|
||||
"logo": "svgs/n8n.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-27T22:41:52-05:00",
|
||||
"template_last_updated_at": "2026-03-30T20:40:07+02:00",
|
||||
"port": "5678"
|
||||
},
|
||||
"navidrome": {
|
||||
|
|
@ -3328,7 +3328,7 @@
|
|||
"category": "vpn",
|
||||
"logo": "svgs/netbird.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-16T10:33:10+05:30"
|
||||
"template_last_updated_at": "2026-04-09T00:00:38+05:30"
|
||||
},
|
||||
"newapi": {
|
||||
"documentation": "https://docs.newapi.pro/en/getting-started/?utm_source=coolify.io",
|
||||
|
|
@ -3360,7 +3360,7 @@
|
|||
"category": "proxy",
|
||||
"logo": "svgs/pangolin-logo.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-12T11:59:36-03:00"
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00"
|
||||
},
|
||||
"next-image-transformation": {
|
||||
"documentation": "https://github.com/coollabsio/next-image-transformation?utm_source=coolify.io",
|
||||
|
|
@ -3392,7 +3392,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nextcloud-with-mysql": {
|
||||
|
|
@ -3409,7 +3409,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nextcloud-with-postgres": {
|
||||
|
|
@ -3426,7 +3426,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nextcloud": {
|
||||
|
|
@ -3443,7 +3443,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/nextcloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-07T23:32:57+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"nexus-arm": {
|
||||
|
|
@ -3554,7 +3554,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/nocodb.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"nodebb": {
|
||||
|
|
@ -3621,7 +3621,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/odoo.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8069"
|
||||
},
|
||||
"ollama-with-open-webui": {
|
||||
|
|
@ -3712,7 +3712,7 @@
|
|||
"category": "email",
|
||||
"logo": "svgs/openarchiver.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-12T23:41:36+01:00",
|
||||
"template_last_updated_at": "2026-05-09T19:35:15+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"open-webui": {
|
||||
|
|
@ -3770,7 +3770,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/openobserve.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": null,
|
||||
"template_last_updated_at": "2026-05-19T16:40:18+05:30",
|
||||
"port": "5080"
|
||||
},
|
||||
"openpanel": {
|
||||
|
|
@ -3789,7 +3789,7 @@
|
|||
"category": "analytics",
|
||||
"logo": "svgs/openpanel.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-27T20:48:57+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"opnform": {
|
||||
|
|
@ -3810,7 +3810,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svg/opnform.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-24T10:13:08+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"orangehrm": {
|
||||
|
|
@ -3828,7 +3828,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/orangehrm.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"organizr": {
|
||||
|
|
@ -3857,7 +3857,7 @@
|
|||
"category": "helpdesk",
|
||||
"logo": "svgs/osticket.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"overseerr": {
|
||||
|
|
@ -3891,7 +3891,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/owncloud.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-06-02T12:12:42+03:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"pairdrop": {
|
||||
|
|
@ -3918,7 +3918,7 @@
|
|||
"category": "games",
|
||||
"logo": "svgs/default.webp",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-15T21:55:50+01:00"
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00"
|
||||
},
|
||||
"paperless": {
|
||||
"documentation": "https://docs.paperless-ngx.com/configuration/?utm_source=coolify.io",
|
||||
|
|
@ -3959,7 +3959,7 @@
|
|||
"category": "finance",
|
||||
"logo": "svgs/paymenter.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-11-23T12:08:21+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "80"
|
||||
},
|
||||
"penpot-with-s3": {
|
||||
|
|
@ -4075,7 +4075,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/plane.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-24T02:34:35+05:30",
|
||||
"template_last_updated_at": "2026-04-24T00:12:17+05:30",
|
||||
"port": "80"
|
||||
},
|
||||
"plex": {
|
||||
|
|
@ -4108,7 +4108,7 @@
|
|||
"category": "email",
|
||||
"logo": "svgs/plunk.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"pocket-id-with-postgresql": {
|
||||
|
|
@ -4265,7 +4265,7 @@
|
|||
"category": "proxy",
|
||||
"logo": "svgs/hoppscotch.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-13T19:24:05+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "9159"
|
||||
},
|
||||
"qbittorrent": {
|
||||
|
|
@ -4351,7 +4351,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/rallly.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-03-19T16:11:50+01:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"reactive-resume": {
|
||||
|
|
@ -4428,7 +4428,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/redmine.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-16T22:27:56+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"rivet-engine": {
|
||||
|
|
@ -4446,7 +4446,7 @@
|
|||
"category": "development",
|
||||
"logo": "svgs/rivet.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-10-22T16:07:42+02:00",
|
||||
"template_last_updated_at": "2026-04-01T19:58:30+02:00",
|
||||
"port": "6420"
|
||||
},
|
||||
"rocketchat": {
|
||||
|
|
@ -4502,7 +4502,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/ryot.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-05-12T08:34:57+02:00",
|
||||
"port": "8000"
|
||||
},
|
||||
"satisfactory": {
|
||||
|
|
@ -4621,7 +4621,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/sftpgo.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-12T23:44:06+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"shlink": {
|
||||
|
|
@ -4660,7 +4660,7 @@
|
|||
"category": "monitoring",
|
||||
"logo": "svgs/signoz.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-11T17:51:10+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"silverbullet": {
|
||||
|
|
@ -4675,7 +4675,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/silverbullet.png",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-05T15:42:12+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"siyuan": {
|
||||
|
|
@ -4759,7 +4759,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/soketi-app-manager.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-16T22:27:17+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
},
|
||||
"soketi": {
|
||||
|
|
@ -4898,7 +4898,7 @@
|
|||
"category": "backend",
|
||||
"logo": "svgs/supabase.svg",
|
||||
"minversion": "4.0.0-beta.228",
|
||||
"template_last_updated_at": "2026-01-20T19:10:07+01:00",
|
||||
"template_last_updated_at": "2026-04-05T20:21:11+02:00",
|
||||
"port": "8000"
|
||||
},
|
||||
"superset-with-postgresql": {
|
||||
|
|
@ -5176,7 +5176,7 @@
|
|||
"category": "productivity",
|
||||
"logo": "svgs/twenty.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-08T18:51:23+01:00",
|
||||
"template_last_updated_at": "2026-04-16T23:18:19+05:30",
|
||||
"port": "3000"
|
||||
},
|
||||
"typesense": {
|
||||
|
|
@ -5224,7 +5224,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/unleash.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-01-04T19:45:35+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "4242"
|
||||
},
|
||||
"unleash-without-database": {
|
||||
|
|
@ -5241,7 +5241,7 @@
|
|||
"category": "devtools",
|
||||
"logo": "svgs/unleash.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-08-17T18:23:57+02:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "4242"
|
||||
},
|
||||
"unstructured": {
|
||||
|
|
@ -5339,7 +5339,7 @@
|
|||
"category": "email",
|
||||
"logo": "svgs/usesend.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2025-12-07T17:32:23+11:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "3000"
|
||||
},
|
||||
"vaultwarden": {
|
||||
|
|
@ -5774,7 +5774,7 @@
|
|||
"category": "storage",
|
||||
"logo": "svgs/cells.svg",
|
||||
"minversion": "0.0.0",
|
||||
"template_last_updated_at": "2026-02-13T18:47:54+01:00",
|
||||
"template_last_updated_at": "2026-04-06T11:35:16-05:00",
|
||||
"port": "8080"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -996,6 +996,35 @@
|
|||
});
|
||||
});
|
||||
|
||||
describe('service application lifecycle command escaping', function () {
|
||||
test('service application deploy action escapes docker command arguments', function () {
|
||||
$source = file_get_contents(app_path('Actions/Service/DeployServiceApplication.php'));
|
||||
|
||||
expect($source)->toContain('escapeshellarg($workdir)')
|
||||
->and($source)->toContain('escapeshellarg($composeFile)')
|
||||
->and($source)->toContain('escapeshellarg($service->uuid)')
|
||||
->and($source)->toContain('escapeshellarg($composeServiceName)')
|
||||
->and($source)->toContain('escapeshellarg($service->destination->network)');
|
||||
});
|
||||
|
||||
test('service application restart and stop actions escape container names', function (string $path) {
|
||||
$source = file_get_contents(app_path($path));
|
||||
|
||||
expect($source)->toContain('escapeshellarg($serviceApplication->name');
|
||||
})->with([
|
||||
'restart action' => 'Actions/Service/RestartServiceApplication.php',
|
||||
'stop action' => 'Actions/Service/StopServiceApplication.php',
|
||||
]);
|
||||
|
||||
test('service application logs endpoint escapes container name before docker helpers', function () {
|
||||
$source = file_get_contents(app_path('Http/Controllers/Api/ServiceApplicationsController.php'));
|
||||
|
||||
expect($source)->toContain('$safeContainerName = escapeshellarg($containerName)')
|
||||
->and($source)->toContain('getContainerStatus($server, $safeContainerName)')
|
||||
->and($source)->toContain('getContainerLogs($server, $containerName, $lines)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('install/build/start command validation', function () {
|
||||
test('rejects semicolon injection in install_command', function () {
|
||||
$rules = sharedDataApplications();
|
||||
|
|
|
|||
|
|
@ -20,20 +20,13 @@
|
|||
|
||||
beforeEach(function () {
|
||||
Queue::fake();
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
|
||||
$plainTextToken = Str::random(40);
|
||||
$token = $this->user->tokens()->create([
|
||||
'name' => 'test-token',
|
||||
'token' => hash('sha256', $plainTextToken),
|
||||
'abilities' => ['*'],
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
$this->bearerToken = $token->getKey().'|'.$plainTextToken;
|
||||
$this->bearerToken = createBearerTokenForServiceApplicationsApiTest($this->user, $this->team);
|
||||
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->server->settings->update([
|
||||
|
|
@ -46,6 +39,19 @@
|
|||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
});
|
||||
|
||||
function createBearerTokenForServiceApplicationsApiTest(User $user, Team $team, array $abilities = ['*']): string
|
||||
{
|
||||
$plainTextToken = Str::random(40);
|
||||
$token = $user->tokens()->create([
|
||||
'name' => 'test-token',
|
||||
'token' => hash('sha256', $plainTextToken),
|
||||
'abilities' => $abilities,
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
|
||||
return $token->getKey().'|'.$plainTextToken;
|
||||
}
|
||||
|
||||
function createServiceWithApplicationForApiTest(object $ctx): object
|
||||
{
|
||||
$service = Service::factory()->create([
|
||||
|
|
@ -108,6 +114,32 @@ function createServiceWithoutApplicationsForApiTest(object $ctx): Service
|
|||
$response->assertStatus(404);
|
||||
$response->assertJsonFragment(['message' => 'Service not found.']);
|
||||
});
|
||||
|
||||
test('does not list applications from another team', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherServer = Server::factory()->create(['team_id' => $otherTeam->id]);
|
||||
$otherServer->settings->update([
|
||||
'is_reachable' => true,
|
||||
'is_usable' => true,
|
||||
'force_disabled' => false,
|
||||
]);
|
||||
|
||||
$otherCtx = (object) [
|
||||
'server' => $otherServer,
|
||||
'destination' => StandaloneDocker::where('server_id', $otherServer->id)->first(),
|
||||
'environment' => Environment::factory()->create([
|
||||
'project_id' => Project::factory()->create(['team_id' => $otherTeam->id])->id,
|
||||
]),
|
||||
];
|
||||
$ctx = createServiceWithApplicationForApiTest($otherCtx);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
])->getJson("/api/v1/services/{$ctx->service->uuid}/applications");
|
||||
|
||||
$response->assertStatus(404);
|
||||
$response->assertJsonFragment(['message' => 'Service not found.']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/v1/services/{uuid}/applications/{app_uuid}', function () {
|
||||
|
|
@ -149,7 +181,7 @@ function createServiceWithoutApplicationsForApiTest(object $ctx): Service
|
|||
'human_name' => 'x',
|
||||
], ['Accept' => 'application/json', 'Content-Type' => 'application/json']);
|
||||
|
||||
$response->assertStatus(400);
|
||||
$response->assertStatus(401);
|
||||
});
|
||||
|
||||
test('updates human_name', function () {
|
||||
|
|
@ -191,13 +223,51 @@ function createServiceWithoutApplicationsForApiTest(object $ctx): Service
|
|||
$response->assertStatus(422);
|
||||
expect((string) $response->json('errors.is_log_drain_enabled.0'))->toContain('Log drain');
|
||||
});
|
||||
|
||||
test('read-only token cannot update a service application', function () {
|
||||
$ctx = createServiceWithApplicationForApiTest($this);
|
||||
$readOnlyToken = createBearerTokenForServiceApplicationsApiTest($this->user, $this->team, ['read']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$readOnlyToken,
|
||||
])->patchJson("/api/v1/services/{$ctx->service->uuid}/applications/{$ctx->serviceApplication->uuid}", [
|
||||
'human_name' => 'Blocked',
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
|
||||
test('returns 409 for domain conflicts unless forced', function () {
|
||||
$ctx = createServiceWithApplicationForApiTest($this);
|
||||
$ctx->serviceApplication->update(['fqdn' => 'http://used.example.com']);
|
||||
$otherApplication = ServiceApplication::create([
|
||||
'name' => 'admin',
|
||||
'service_id' => $ctx->service->id,
|
||||
'image' => 'nginx:alpine',
|
||||
]);
|
||||
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
])->patchJson("/api/v1/services/{$ctx->service->uuid}/applications/{$otherApplication->uuid}", [
|
||||
'url' => 'http://used.example.com',
|
||||
])->assertStatus(409);
|
||||
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
])->patchJson("/api/v1/services/{$ctx->service->uuid}/applications/{$otherApplication->uuid}?force_domain_override=true", [
|
||||
'url' => 'http://used.example.com',
|
||||
])->assertStatus(200);
|
||||
|
||||
$otherApplication->refresh();
|
||||
expect($otherApplication->fqdn)->toBe('http://used.example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/v1/services/{uuid}/applications/{app_uuid}/restart', function () {
|
||||
test('returns 400 without valid token', function () {
|
||||
$response = $this->postJson('/api/v1/services/some-uuid/applications/some-app/restart');
|
||||
|
||||
$response->assertStatus(400);
|
||||
$response->assertStatus(401);
|
||||
});
|
||||
|
||||
test('queues restart for a service application', function () {
|
||||
|
|
@ -214,6 +284,17 @@ function createServiceWithoutApplicationsForApiTest(object $ctx): Service
|
|||
});
|
||||
|
||||
describe('POST /api/v1/services/{uuid}/applications/{app_uuid}/start', function () {
|
||||
test('write token cannot start a service application', function () {
|
||||
$ctx = createServiceWithApplicationForApiTest($this);
|
||||
$writeToken = createBearerTokenForServiceApplicationsApiTest($this->user, $this->team, ['write']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$writeToken,
|
||||
])->postJson("/api/v1/services/{$ctx->service->uuid}/applications/{$ctx->serviceApplication->uuid}/start");
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
|
||||
test('queues deploy for a service application', function () {
|
||||
$ctx = createServiceWithApplicationForApiTest($this);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue