feat: Use compose instead of normal docker cmd

This commit is contained in:
Andras Bacsai 2022-03-23 10:25:32 +01:00
parent f0ab3750bd
commit 2bf73109b2
13 changed files with 131 additions and 191 deletions

View File

@ -30,6 +30,7 @@
"@sveltejs/kit": "1.0.0-next.288", "@sveltejs/kit": "1.0.0-next.288",
"@types/bcrypt": "5.0.0", "@types/bcrypt": "5.0.0",
"@types/js-cookie": "3.0.1", "@types/js-cookie": "3.0.1",
"@types/js-yaml": "^4.0.5",
"@types/node": "17.0.21", "@types/node": "17.0.21",
"@types/node-forge": "1.0.0", "@types/node-forge": "1.0.0",
"@typescript-eslint/eslint-plugin": "4.31.1", "@typescript-eslint/eslint-plugin": "4.31.1",

9
pnpm-lock.yaml generated
View File

@ -9,6 +9,7 @@ specifiers:
'@sveltejs/kit': 1.0.0-next.288 '@sveltejs/kit': 1.0.0-next.288
'@types/bcrypt': 5.0.0 '@types/bcrypt': 5.0.0
'@types/js-cookie': 3.0.1 '@types/js-cookie': 3.0.1
'@types/js-yaml': ^4.0.5
'@types/node': 17.0.21 '@types/node': 17.0.21
'@types/node-forge': 1.0.0 '@types/node-forge': 1.0.0
'@typescript-eslint/eslint-plugin': 4.31.1 '@typescript-eslint/eslint-plugin': 4.31.1
@ -84,6 +85,7 @@ devDependencies:
'@sveltejs/kit': 1.0.0-next.288_svelte@3.46.4 '@sveltejs/kit': 1.0.0-next.288_svelte@3.46.4
'@types/bcrypt': 5.0.0 '@types/bcrypt': 5.0.0
'@types/js-cookie': 3.0.1 '@types/js-cookie': 3.0.1
'@types/js-yaml': 4.0.5
'@types/node': 17.0.21 '@types/node': 17.0.21
'@types/node-forge': 1.0.0 '@types/node-forge': 1.0.0
'@typescript-eslint/eslint-plugin': 4.31.1_386b67ad67ef29c6a0ccaf3e9b60f945 '@typescript-eslint/eslint-plugin': 4.31.1_386b67ad67ef29c6a0ccaf3e9b60f945
@ -537,6 +539,13 @@ packages:
} }
dev: true dev: true
/@types/js-yaml/4.0.5:
resolution:
{
integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==
}
dev: true
/@types/json-schema/7.0.9: /@types/json-schema/7.0.9:
resolution: resolution:
{ {

1
src/app.d.ts vendored
View File

@ -9,6 +9,7 @@ declare namespace App {
interface Session extends SessionData {} interface Session extends SessionData {}
interface Stuff { interface Stuff {
application: any; application: any;
isRunning: boolean;
} }
} }

View File

@ -29,10 +29,10 @@ export function makeLabelForStandaloneApplication({
fqdn = `${protocol}://${pullmergeRequestId}.${domain}`; fqdn = `${protocol}://${pullmergeRequestId}.${domain}`;
} }
return [ return [
'--label coolify.managed=true', 'coolify.managed=true',
`--label coolify.version=${version}`, `coolify.version=${version}`,
`--label coolify.type=standalone-application`, `coolify.type=standalone-application`,
`--label coolify.configuration=${base64Encode( `coolify.configuration=${base64Encode(
JSON.stringify({ JSON.stringify({
applicationId, applicationId,
fqdn, fqdn,

View File

@ -6,13 +6,6 @@ const createDockerfile = async (data, image): Promise<void> => {
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`LABEL coolify.image=true`);
if (data.phpModules?.length > 0) {
Dockerfile.push(
`ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/`
);
Dockerfile.push(`RUN chmod +x /usr/local/bin/install-php-extensions`);
Dockerfile.push(`RUN /usr/local/bin/install-php-extensions ${data.phpModules.join(' ')}`);
}
Dockerfile.push('WORKDIR /app'); Dockerfile.push('WORKDIR /app');
Dockerfile.push(`COPY .${baseDirectory || ''} /app`); Dockerfile.push(`COPY .${baseDirectory || ''} /app`);
Dockerfile.push(`COPY /.htaccess .`); Dockerfile.push(`COPY /.htaccess .`);

View File

@ -9,7 +9,16 @@ export const dateOptions: DateTimeFormatOptions = {
hour12: false hour12: false
}; };
export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php']; export const staticDeployments = [
'react',
'vuejs',
'static',
'svelte',
'gatsby',
'php',
'astro',
'eleventy'
];
export const notNodeDeployments = ['php', 'docker', 'rust']; export const notNodeDeployments = ['php', 'docker', 'rust'];
export function getDomain(domain) { export function getDomain(domain) {

View File

@ -66,6 +66,7 @@ export async function removeApplication({ id, teamId }) {
await prisma.buildLog.deleteMany({ where: { applicationId: id } }); await prisma.buildLog.deleteMany({ where: { applicationId: id } });
await prisma.build.deleteMany({ where: { applicationId: id } }); await prisma.build.deleteMany({ where: { applicationId: id } });
await prisma.secret.deleteMany({ where: { applicationId: id } }); await prisma.secret.deleteMany({ where: { applicationId: id } });
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } }); await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
} }
@ -157,9 +158,6 @@ export async function getApplication({ id, teamId }) {
return s; return s;
}); });
} }
if (body?.phpModules) {
body.phpModules = body.phpModules.split(',');
}
return { ...body }; return { ...body };
} }
@ -215,8 +213,7 @@ export async function configureApplication({
buildCommand, buildCommand,
startCommand, startCommand,
baseDirectory, baseDirectory,
publishDirectory, publishDirectory
phpModules
}) { }) {
return await prisma.application.update({ return await prisma.application.update({
where: { id }, where: { id },
@ -229,8 +226,7 @@ export async function configureApplication({
startCommand, startCommand,
baseDirectory, baseDirectory,
publishDirectory, publishDirectory,
name, name
phpModules
} }
}); });
} }

View File

@ -19,6 +19,7 @@ import {
makeLabelForStandaloneApplication, makeLabelForStandaloneApplication,
setDefaultConfiguration setDefaultConfiguration
} from '$lib/buildPacks/common'; } from '$lib/buildPacks/common';
import yaml from 'js-yaml';
export default async function (job) { export default async function (job) {
/* /*
@ -71,7 +72,7 @@ export default async function (job) {
let volumes = let volumes =
persistentStorage?.map((storage) => { persistentStorage?.map((storage) => {
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${ return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
type !== 'docker' ? '/app/' : '' type !== 'docker' ? '/app' : ''
}${storage.path}`; }${storage.path}`;
}) || []; }) || [];
// Previews, we need to get the source branch and set subdomain // Previews, we need to get the source branch and set subdomain
@ -261,23 +262,53 @@ export default async function (job) {
} }
try { try {
saveBuildLog({ line: 'Deployment started.', buildId, applicationId }); saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
for await (const volume of volumes) { // for await (const volume of volumes) {
const id = volume.split(':')[0]; // const id = volume.split(':')[0];
try { // try {
await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`); // await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`);
} catch (error) { // } catch (error) {
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`); // await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`);
// }
// }
const composeVolumes = volumes.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
} }
};
});
const compose = {
version: '3.8',
services: {
[imageId]: {
image: `${applicationId}:${tag}`,
container_name: imageId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
networks: [docker.network],
labels: labels,
depends_on: [],
restart: 'always'
} }
volumes = volumes.map((volume) => `-v ${volume} `).join(); },
const { stderr } = await asyncExecShell( networks: {
`DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join( [docker.network]: {
' ' external: true
)} --name ${imageId} --network ${docker.network} --restart always ${ }
volumes.length > 0 ? volumes : '' },
} -d ${applicationId}:${tag}` volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(compose));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
); );
if (stderr) console.log(stderr);
// const { stderr } = await asyncExecShell(
// `DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join(
// ' '
// )} --name ${imageId} --network ${docker.network} --restart always ${volumes.length > 0 ? volumes : ''
// } -d ${applicationId}:${tag}`
// );
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
} catch (error) { } catch (error) {
saveBuildLog({ line: error, buildId, applicationId }); saveBuildLog({ line: error, buildId, applicationId });

View File

@ -52,8 +52,7 @@ export const post: RequestHandler = async (event) => {
buildCommand, buildCommand,
startCommand, startCommand,
baseDirectory, baseDirectory,
publishDirectory, publishDirectory
phpModules
} = await event.request.json(); } = await event.request.json();
if (port) port = Number(port); if (port) port = Number(port);
@ -69,8 +68,7 @@ export const post: RequestHandler = async (event) => {
buildCommand, buildCommand,
startCommand, startCommand,
baseDirectory, baseDirectory,
publishDirectory, publishDirectory
phpModules
}); });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {

View File

@ -47,125 +47,7 @@
import { post } from '$lib/api'; import { post } from '$lib/api';
import cuid from 'cuid'; import cuid from 'cuid';
import { browser } from '$app/env'; import { browser } from '$app/env';
import Select from 'svelte-select';
const { id } = $page.params; const { id } = $page.params;
let collection = [
'amqp',
'apcu',
'apcu_bc',
'ast',
'bcmath',
'blackfire',
'bz2',
'calendar',
'cmark',
'csv',
'dba',
'decimal',
'ds',
'enchant',
'ev',
'event',
'excimer',
'exif',
'ffi',
'gd',
'gearman',
'geoip',
'geospatial',
'gettext',
'gmagick',
'gmp',
'gnupg',
'grpc',
'http',
'igbinary',
'imagick',
'imap',
'inotify',
'interbase',
'intl',
'ioncube_loader',
'jsmin',
'json_post',
'ldap',
'lzf',
'mailparse',
'maxminddb',
'mcrypt',
'memcache',
'memcached',
'mongo',
'mongodb',
'mosquitto',
'msgpack',
'mssql',
'mysqli',
'oauth',
'oci8',
'odbc',
'opcache',
'opencensus',
'openswoole',
'parallel',
'pcntl',
'pcov',
'pdo_dblib',
'pdo_firebird',
'pdo_mysql',
'pdo_oci',
'pdo_odbc',
'pdo_pgsql',
'pdo_sqlsrv',
'pgsql',
'propro',
'protobuf',
'pspell',
'pthreads',
'raphf',
'rdkafka',
'recode',
'redis',
'seaslog',
'shmop',
'smbclient',
'snmp',
'snuffleupagus',
'soap',
'sockets',
'solr',
'sourceguardian',
'spx',
'sqlsrv',
'ssh2',
'stomp',
'swoole',
'sybase_ct',
'sysvmsg',
'sysvsem',
'sysvshm',
'tensor',
'tidy',
'timezonedb',
'uopz',
'uploadprogress',
'uuid',
'vips',
'wddx',
'xdebug',
'xhprof',
'xlswriter',
'xmldiff',
'xmlrpc',
'xsl',
'yac',
'yaml',
'yar',
'zephir_parser',
'zip',
'zookeeper',
'zstd'
];
let domainEl: HTMLInputElement; let domainEl: HTMLInputElement;
@ -225,9 +107,8 @@
async function handleSubmit() { async function handleSubmit() {
loading = true; loading = true;
try { try {
const tempPhpModules = application.phpModules?.map((module) => module.value).toString() || '';
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave }); await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave });
await post(`/applications/${id}.json`, { ...application, phpModules: tempPhpModules }); await post(`/applications/${id}.json`, { ...application });
return window.location.reload(); return window.location.reload();
} catch ({ error }) { } catch ({ error }) {
if (error.startsWith('DNS not set')) { if (error.startsWith('DNS not set')) {
@ -481,19 +362,6 @@
/> />
</div> </div>
{/if} {/if}
<!-- {#if application.buildPack === 'php'}
<div class="grid grid-cols-2 items-center">
<label for="startCommand" class="text-base font-bold text-stone-100">PHP Modules</label>
<div class="svelte-select">
<Select
isMulti={true}
bind:value={application.phpModules}
items={collection}
placeholder="Select PHP modules to add..."
/>
</div>
</div>
{/if} -->
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<div class="flex-col"> <div class="flex-col">
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100" <label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"

View File

@ -6,17 +6,31 @@
}; };
import { del, post } from '$lib/api'; import { del, post } from '$lib/api';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { createEventDispatcher } from 'svelte';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
const { id } = $page.params; const { id } = $page.params;
async function saveStorage() { const dispatch = createEventDispatcher();
async function saveStorage(newStorage = false) {
try { try {
if (!storage.path) return errorNotification('Path is required.');
storage.path = storage.path.startsWith('/') ? storage.path : `/${storage.path}`; storage.path = storage.path.startsWith('/') ? storage.path : `/${storage.path}`;
storage.path = storage.path.endsWith('/') ? storage.path.slice(0, -1) : storage.path; storage.path = storage.path.endsWith('/') ? storage.path.slice(0, -1) : storage.path;
storage.path.replace(/\/\//g, '/'); storage.path.replace(/\/\//g, '/');
await post(`/applications/${id}/storage.json`, { await post(`/applications/${id}/storage.json`, {
path: storage.path path: storage.path,
storageId: storage.id,
newStorage
}); });
dispatch('refresh');
if (isNew) {
storage.path = null;
storage.id = null;
}
if (newStorage) toast.push('Storage saved.');
else toast.push('Storage updated.');
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@ -24,6 +38,8 @@
async function removeStorage() { async function removeStorage() {
try { try {
await del(`/applications/${id}/storage.json`, { path: storage.path }); await del(`/applications/${id}/storage.json`, { path: storage.path });
dispatch('refresh');
toast.push('Storage deleted.');
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@ -32,7 +48,6 @@
<td> <td>
<input <input
readonly={!isNew}
bind:value={storage.path} bind:value={storage.path}
required required
placeholder="eg: /sqlite.db" placeholder="eg: /sqlite.db"
@ -40,10 +55,19 @@
/> />
</td> </td>
<td> <td>
<div class="flex items-center justify-center px-2"> {#if isNew}
<button class="bg-green-600 hover:bg-green-500" on:click={saveStorage}>Add</button> <div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveStorage(true)}>Add</button
>
</div> </div>
<div class="flex items-center justify-center px-2"> {:else}
<button class="bg-green-600 hover:bg-green-500" on:click={removeStorage}>Remove</button> <div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="" on:click={() => saveStorage(false)}>Set</button>
</div> </div>
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}>Remove</button>
</div>
</div>
{/if}
</td> </td>

View File

@ -1,9 +1,7 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import jsonwebtoken from 'jsonwebtoken';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event, false); const { status, body, teamId } = await getUserDetails(event, false);
@ -27,11 +25,18 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
const { path } = await event.request.json(); const { path, newStorage, storageId } = await event.request.json();
try { try {
if (newStorage) {
await db.prisma.applicationPersistentStorage.create({ await db.prisma.applicationPersistentStorage.create({
data: { path, application: { connect: { id } } } data: { path, application: { connect: { id } } }
}); });
} else {
await db.prisma.applicationPersistentStorage.update({
where: { id: storageId },
data: { path }
});
}
return { return {
status: 201 status: 201
}; };

View File

@ -26,8 +26,13 @@
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { page } from '$app/stores'; import { page } from '$app/stores';
import Storage from './_Storage.svelte'; import Storage from './_Storage.svelte';
import { get } from '$lib/api';
const { id } = $page.params; const { id } = $page.params;
async function refreshStorage() {
const data = await get(`/applications/${id}/storage.json`);
persistentStorages = [...data.persistentStorages];
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@ -49,12 +54,12 @@
{#each persistentStorages as storage} {#each persistentStorages as storage}
{#key storage.id} {#key storage.id}
<tr> <tr>
<Storage {storage} /> <Storage on:refresh={refreshStorage} {storage} />
</tr> </tr>
{/key} {/key}
{/each} {/each}
<tr> <tr>
<Storage isNew /> <Storage on:refresh={refreshStorage} isNew />
</tr> </tr>
</tbody> </tbody>
</table> </table>