Compare commits

...

334 Commits

Author SHA1 Message Date
Mauricio Siu 823dbe608f refactor: migration 2024-10-24 21:25:45 -06:00
Mauricio Siu 4309939fd5 refactor: update 2024-10-24 21:07:36 -06:00
Mauricio Siu cb586c9b74 Merge branch 'canary' into feat/payments 2024-10-24 13:50:17 -06:00
Mauricio Siu fa053b4d1f refactor(dokploy): remove stripe from global scope 2024-10-24 01:10:17 -06:00
Mauricio Siu bcc7afa3e4 refactor(dokploy): fix ts errors 2024-10-24 00:55:59 -06:00
Mauricio Siu 647a5d05a6 test(dokploy): add missing fields 2024-10-24 00:55:03 -06:00
Mauricio Siu e15d41f80d chore: add radix tabs 2024-10-24 00:22:57 -06:00
Mauricio Siu 6c7c919d49 refactor: set servers quantity in 0 when the subscription is created 2024-10-24 00:21:28 -06:00
Mauricio Siu 22eb965919 Merge pull request #584 from dmbr0/patch-2
Fix typo in README.md
2024-10-23 20:08:37 -06:00
Alex Whitney c0746b95b3 Fix typo in README.md 2024-10-23 20:18:59 -04:00
Mauricio Siu dfdedf9e48 chore(dokploy): simplify migrations 2024-10-23 01:16:42 -06:00
Mauricio Siu 7e76eb4dd1 refactor: delete log 2024-10-23 01:06:34 -06:00
Mauricio Siu c1f777e23e refactor: remove serverIp 2024-10-23 01:01:04 -06:00
Mauricio Siu 975d13c7e1 refactor(dokploy): disable stats monitoring 2024-10-23 00:58:38 -06:00
Mauricio Siu 017bdd2778 refactor(dokploy): add flag to prevent run commands when is cloud 2024-10-23 00:54:40 -06:00
Mauricio Siu 548df8c0f4 Merge branch 'canary' into feat/payments 2024-10-22 20:31:18 -06:00
Mauricio Siu 8faa6ae1cf chore(version): bump version 2024-10-22 20:29:57 -06:00
Mauricio Siu 76ed1107c2 refactor(dokploy): add -r flag to read the enviroments vars 2024-10-22 20:19:49 -06:00
Mauricio Siu cff5049096 Merge pull request #579 from Dokploy/578-unable-to-reset-my-password
fix(dokploy): use the exact path of functions #578
2024-10-22 20:02:43 -06:00
Mauricio Siu cb5ca100a6 fix(dokploy): use the exact path of functions #578 2024-10-22 20:00:29 -06:00
Mauricio Siu 03d1e974dd Update LICENSE.MD 2024-10-22 16:53:12 -06:00
Mauricio Siu 1e6dbb5e8e feat(dokploy): add reset password for cloud 2024-10-22 01:25:13 -06:00
Mauricio Siu 431dadb6c2 feat: add pricing 2024-10-22 00:29:17 -06:00
Mauricio Siu 22e42b62ad feat: add is cloud in ssr 2024-10-21 22:38:23 -06:00
Mauricio Siu 49ee8ce132 Merge branch 'canary' into feat/payments 2024-10-21 22:22:59 -06:00
Mauricio Siu b609d72d1c Merge pull request #576 from Dokploy/541-install-failed-due-to-docker-swarm-initialize-failed
fix(installation): exit of script when docker swarm init fails
2024-10-21 21:58:22 -06:00
Mauricio Siu d7071fba60 fix(installation): exit of script when docker swarm init fails 2024-10-21 21:50:26 -06:00
Mauricio Siu 76585991ec Merge pull request #559 from eremannisto/fix/improve-faq-questions-and-answers
fix: Improve `FAQ` questions and answers
2024-10-21 21:31:00 -06:00
Mauricio Siu da6efcf733 Merge pull request #406 from benbristow/fix/directus
fix: directus healthchecks (fix race condition starting up), volumes for uploads & extensions, add secret/db password generation, bump version to 11.0.2
2024-10-21 21:28:52 -06:00
Mauricio Siu 64b0770cfb Merge pull request #575 from Dokploy/574-clone-github-gitlab-and-bitbucket-submodules
feat(dokploy): add recurse submodules to providers #331
2024-10-21 21:25:18 -06:00
Mauricio Siu 1ec83a3236 feat(dokploy): add recurse submodules to providers #331 2024-10-21 21:14:40 -06:00
Mauricio Siu 9bace8e58b refactor: clean stripe customer if the customer is deleted 2024-10-21 13:01:08 -06:00
Mauricio Siu c0afcaf3f6 refactor: show banner when the server is disabled 2024-10-21 02:19:10 -06:00
Mauricio Siu 53edf06476 refactor: remove comments 2024-10-21 00:48:04 -06:00
Mauricio Siu 255e9e4095 refactor: remove dokploy restart on notifications 2024-10-21 00:46:33 -06:00
Mauricio Siu 03f923c6e2 refactor: update show servers 2024-10-21 00:43:04 -06:00
Mauricio Siu 4685ef7439 refactor: update stripe envs 2024-10-21 00:39:14 -06:00
Mauricio Siu 626cfb80b4 refactor: add website url redirect 2024-10-21 00:37:11 -06:00
Mauricio Siu 9591fbff08 refactor: add Is cloud flag 2024-10-21 00:34:59 -06:00
Mauricio Siu fbda00f059 refactor: update webhooks and added validation to prevent deploy when the server is inactive 2024-10-21 00:34:16 -06:00
Mauricio Siu 1907e7e59c feat: add webhook 2024-10-20 23:08:26 -06:00
Mauricio Siu ffe7b04bea feat: add stripe webhooks 2024-10-20 15:08:44 -06:00
Mauricio Siu fe0a662afd feat(cloud): add billing wip 2024-10-20 00:14:27 -06:00
Mauricio Siu 319584d911 Merge branch 'canary' into feat/payments 2024-10-18 21:59:12 -06:00
Mauricio Siu 7d5a660f4d chore: bump version 2024-10-18 21:56:39 -06:00
Mauricio Siu f7f0cbf318 Merge pull request #566 from arioberek/canary
fix: update reset password link URL
2024-10-18 09:34:51 -06:00
Arielton Oberek 841c0731aa fix: remove language prefix from reset password URL 2024-10-18 10:41:07 -03:00
Arielton Oberek 137cd25267 fix: reset password button URL 2024-10-18 10:35:08 -03:00
Mauricio Siu 2d40b2dfe5 feat: add stripe integration 2024-10-18 01:43:45 -06:00
Mauricio Siu e32b713742 Merge branch 'canary' into feat/payments 2024-10-18 01:25:00 -06:00
Mauricio Siu 4dcd16c41e Merge pull request #564 from Dokploy/fix/git-ssh
fix(git): remove old references to ssh files to use the tmp file
2024-10-18 00:28:51 -06:00
Mauricio Siu 60497fe59d refactor: add husky 2024-10-18 00:10:01 -06:00
Mauricio Siu 8536945a60 styles: lint 2024-10-17 23:58:51 -06:00
Mauricio Siu e0a8d8258c fix(git): remove old references to ssh files to use the tmp file 2024-10-17 23:55:27 -06:00
Mauricio Siu 988357fb55 refactor: update payments 2024-10-17 21:52:24 -06:00
Mauricio Siu fe19cdb5e4 fix(setup): import directly from specific path 2024-10-16 15:57:51 -06:00
Mauricio Siu c4654a9619 chore(contributing): update contributing 2024-10-16 13:23:25 -06:00
Ere Männistö 0a123a652b Improve FAQ questions and answers
- Improve questions and answers
- Fix typos
2024-10-15 19:54:41 +03:00
Mauricio Siu 5d437c29b2 refactor: remove header and navbar 2024-10-13 22:58:01 -06:00
Mauricio Siu 53f345ab1d feat: add privacy & terms 2024-10-13 22:54:02 -06:00
Mauricio Siu 8785282133 chore(version): bump version 2024-10-13 11:51:52 -06:00
Mauricio Siu 35c084af1d Merge pull request #550 from Dokploy/fix/env-parsing
fix(logs): improve logs in remote server when is error, and fix the e…
2024-10-13 11:01:28 -06:00
Mauricio Siu b63488baba Merge pull request #547 from Dokploy/536-implement-custom-certificates-in-external-server
feat(certificates): create certificates in a remote server
2024-10-13 11:01:16 -06:00
Mauricio Siu 7e5f21b28e style: lint 2024-10-13 02:28:04 -06:00
Mauricio Siu 8d41bafb93 fix(logs): improve logs in remote server when is error, and fix the env parsing to allow any values in enviroment variables 2024-10-13 02:27:33 -06:00
Mauricio Siu 6f5049efd5 styles: lint 2024-10-12 19:33:56 -06:00
Mauricio Siu 6dd6b636e5 feat(certificates): create certificates in a remote server 2024-10-12 19:09:50 -06:00
Mauricio Siu 8488d530f3 Merge pull request #546 from Dokploy/542-cannot-save-server-address-without-lets-encrypt-email
fix(traefik): allow to save domain without letsencrypt email when the…
2024-10-12 16:58:50 -06:00
Mauricio Siu 6c61d5cdf5 Merge pull request #545 from mezotv/fix-sponsor-size
fix(website): improve sponsor logo alignment
2024-10-12 16:45:40 -06:00
Mauricio Siu 8036455c2d fix(traefik): allow to save domain without letsencrypt email when the cert is none #542 2024-10-12 16:44:10 -06:00
Dominik Koch bf44eeab3d style: format code 2024-10-12 22:29:32 +00:00
Dominik Koch 0cd185696d fix: add target blank to links 2024-10-12 22:26:48 +00:00
Mauricio Siu 339697437a Merge pull request #544 from Dokploy/533-the-traefik-labels-get-duplicated-if-i-have-two-services-and-two-domains-in-one-docker-compose-file
fix(compose): refetch compose file when enter to the modal to prevent…
2024-10-12 16:26:43 -06:00
Dominik Koch e0b15fe971 fix: logo alignment 2024-10-12 22:23:07 +00:00
Mauricio Siu afc5ea43da fix(compose): refetch compose file when enter to the modal to prevent show duplicate labels 2024-10-12 16:19:49 -06:00
Mauricio Siu bb337e819e Merge pull request #539 from mezotv/fix-typo
Fix typos and grammar errors
2024-10-12 16:11:27 -06:00
Mauricio Siu 160dd10f77 Merge pull request #538 from mezotv/update-website-sponsors
Add organization sponsors to website
2024-10-12 16:10:46 -06:00
Dominik Koch 49b096fef6 style: format code 2024-10-12 21:08:58 +00:00
Dominik Koch 4828b840cb fix: lxaer logo 2024-10-12 23:05:30 +02:00
Dominik Koch 67af70448b Update README.md 2024-10-12 23:03:09 +02:00
Dominik Koch 946acf5245 style: format code 2024-10-12 20:59:49 +00:00
Dominik Koch 390b3a835a fix: new lxaer logo 2024-10-12 22:54:46 +02:00
Dominik Koch 2842bf9a91 Update README.md 2024-10-12 22:51:56 +02:00
Dominik Koch 8a0e10f6f4 Update README.md 2024-10-12 22:51:03 +02:00
Mauricio Siu 67efa82b91 Merge pull request #537 from mezotv/organization-readme
Add missing organization to readme
2024-10-11 12:41:27 -06:00
Dominik Koch 546d6b87ea fix: get rid of typos and fix grammar 2024-10-11 20:03:20 +02:00
Dominik Koch d012d19253 feat(web): add organization sponsors 2024-10-11 11:28:10 +02:00
Dominik Koch e925ed9ea4 fix: add missing organization to readme 2024-10-11 11:21:20 +02:00
Mauricio Siu 0c05809d7d refactor: remove 2024-10-10 23:38:16 -06:00
Mauricio Siu 29f1631950 refactor: add dynamic import queue 2024-10-10 23:34:56 -06:00
Mauricio Siu 9c0c58035a chore; add logs for process env 2024-10-10 22:40:02 -06:00
Mauricio Siu f4262569dd chore: add cat 2024-10-10 22:37:36 -06:00
Mauricio Siu 99cf6eae49 refactor: adjust enviroment variables 2024-10-10 22:27:25 -06:00
Mauricio Siu 0488546706 refactor: print env 2024-10-10 22:17:19 -06:00
Mauricio Siu dc32cd71e5 refactor: add env to dist 2024-10-10 22:01:14 -06:00
Mauricio Siu efdfd5d13c chore: print envs 2024-10-10 21:52:50 -06:00
Mauricio Siu d31cab76f0 refactor: add flag 2024-10-10 21:52:09 -06:00
Mauricio Siu 00ed202127 refactor: add .env 2024-10-10 21:40:43 -06:00
Mauricio Siu aaa4ca297d refacctor: add missing envs 2024-10-10 21:32:27 -06:00
Mauricio Siu 629871e683 refactor: add option to ci/cd 2024-10-10 21:22:13 -06:00
Mauricio Siu a237c651c3 chore: add autoprefixer 2024-10-07 01:47:01 -06:00
Mauricio Siu 4f9b0d9d59 chore: remove .env 2024-10-07 01:45:16 -06:00
Mauricio Siu b701a0b504 Merge pull request #531 from Dokploy/feat/cloud
Feat/cloud
2024-10-07 01:44:00 -06:00
Mauricio Siu 39036202bb test: fix mock zip drop 2024-10-07 01:34:35 -06:00
Mauricio Siu 4225ad83e7 chore: add docker images 2024-10-07 01:28:22 -06:00
Mauricio Siu 38c4e0ede1 chore: add build server 2024-10-07 01:16:59 -06:00
Mauricio Siu 25a64c703f chore: add pnpm run build 2024-10-07 01:08:47 -06:00
Mauricio Siu ab5871add7 chore: biome 2024-10-07 01:05:47 -06:00
Mauricio Siu 2b0e009f6a refactor: remove feature tag 2024-10-07 00:59:07 -06:00
Mauricio Siu 9b6ea99eea refactor: remove unused files 2024-10-07 00:49:54 -06:00
Mauricio Siu c4cf545d85 Merge branch 'canary' into feat/cloud 2024-10-07 00:31:23 -06:00
Mauricio Siu 1edd717432 Merge pull request #524 from lorenzomigliorero/feat/domains-link
feat: add dropdown link
2024-10-07 00:26:23 -06:00
Mauricio Siu 5b88af6158 refactor: remove findAdmin 2024-10-06 15:04:54 -06:00
Mauricio Siu e995d894d8 refactor: remove docker from cloud 2024-10-06 14:27:14 -06:00
Mauricio Siu 5f56512e56 refactor: update queue jobs 2024-10-06 14:16:31 -06:00
Mauricio Siu 24e4930fc1 refactor: use connection IORedis 2024-10-06 02:57:46 -06:00
Mauricio Siu 541728805f refactor: add health path to middleware 2024-10-06 02:46:07 -06:00
Mauricio Siu 9e4bac1386 refactor: update ioredis connection 2024-10-06 02:44:35 -06:00
Mauricio Siu ed8d32d050 chore: add type module 2024-10-06 02:25:02 -06:00
Mauricio Siu 7fb66bc58b refactor:add remote cron jobs 2024-10-06 02:19:15 -06:00
Mauricio Siu 58c06fba86 refactor(cloud): add api key for autentication between servers 2024-10-06 01:56:53 -06:00
Mauricio Siu 3cf27a068a refactor: add authorization 2024-10-06 01:37:39 -06:00
Mauricio Siu 7cfbea3f60 refactor: update 2024-10-05 22:18:33 -06:00
Mauricio Siu 7907e33431 refactor: update namefile 2024-10-05 22:17:44 -06:00
Mauricio Siu 89f3078ce5 refactor: update package name 2024-10-05 22:15:57 -06:00
Mauricio Siu f3ce69b656 refactor: rename builders to server 2024-10-05 22:15:47 -06:00
Mauricio Siu 43555cdabe feat(schedules): add schedules server 2024-10-05 22:11:38 -06:00
Mauricio Siu 651bf3a303 refactor: update ref 2024-10-05 18:42:08 -06:00
Mauricio Siu 4cde1a8a7d refactor: close docker logs 2024-10-05 18:20:28 -06:00
Mauricio Siu 405efcac0b refactor: update logs listener 2024-10-05 17:34:17 -06:00
Mauricio Siu 8397de0dca refactor: close connections when the ws is not ready 2024-10-05 17:17:46 -06:00
Mauricio Siu 06b58e6495 refactor: close connection ws 2024-10-05 16:46:02 -06:00
Mauricio Siu 24db4006cf refactor: add close ws 2024-10-05 16:45:53 -06:00
Mauricio Siu 84009c5e9b refactor: add close websocket 2024-10-05 16:04:28 -06:00
Mauricio Siu e32afde973 refactor: add status ok 2024-10-05 14:37:45 -06:00
Mauricio Siu fecffac573 refactor: test rollback 2024-10-05 14:27:00 -06:00
Mauricio Siu b4448e013c test: rollback 2024-10-05 14:16:23 -06:00
Mauricio Siu c3f06a6272 feat(cloud): add healtchecks 2024-10-05 13:34:00 -06:00
Mauricio Siu 2be724f780 Update README.md 2024-10-05 02:39:50 -06:00
Mauricio Siu bf78326c96 chore: use the same tailwindcss version 2024-10-05 01:30:09 -06:00
Mauricio Siu 4ca8722c6e refactor: optimize dockerfile 2024-10-05 01:21:39 -06:00
Mauricio Siu e56e1eb687 refactor: add autoprefixer 2024-10-05 01:15:52 -06:00
Mauricio Siu 5a5c302bdc chore: optimize image 2024-10-05 01:11:15 -06:00
Mauricio Siu 997dc85985 refactor(api): remove deploy normal compose on the same dokploy server 2024-10-05 00:34:41 -06:00
Mauricio Siu 09ef851372 refactor(cloud): add validation to prevent create applications without server 2024-10-04 21:31:22 -06:00
Mauricio Siu 7c4987d84d refactor(cloud): add validation to prevent access to shared resources 2024-10-04 20:44:57 -06:00
Mauricio Siu 5cebf5540a refactor(cloud): add deploy to external API 2024-10-04 18:53:46 -06:00
Mauricio Siu 3df2f8e58c refactor(terminal): use ssh2 instead of cmd 2024-10-04 17:24:17 -06:00
Mauricio Siu a642d36a23 refactor: hide handler when is cloud version 2024-10-04 15:20:47 -06:00
Mauricio Siu 72bceec62d refactor: add try catch 2024-10-04 15:16:01 -06:00
Mauricio Siu 3d32314e80 chore: update lock 2024-10-04 15:14:56 -06:00
Mauricio Siu 7259830ac1 chore: add experimental specifier resolution flag 2024-10-04 15:14:30 -06:00
Mauricio Siu daa87c0dc7 refactor(cloud): add is cloud flag to cluters 2024-10-04 14:40:31 -06:00
Mauricio Siu a2ee55e0e9 Merge pull request #527 from kikoncuo/fix/application-create-missing-return
fix: Application create endpoint returns value
2024-10-04 13:39:21 -06:00
Enrique de6aeac243 fix(application.create): add missing return statement and align response with application.one 2024-10-04 11:46:14 +02:00
Lorenzo Migliorero f640b4a87f stop propagation 2024-10-04 11:03:11 +02:00
Mauricio Siu 3747db08d4 refactor(cloud): add validation to prevent execute in cloud version 2024-10-04 01:12:14 -06:00
Mauricio Siu ab4677ac0e refactor(auth): set null when the findAdmin is null 2024-10-04 01:01:30 -06:00
Mauricio Siu 172d55311e chore(cloud): add migrations 2024-10-04 00:04:52 -06:00
Mauricio Siu 3ce25e2ac8 chore(migrations): remove migrations 2024-10-03 23:47:34 -06:00
Mauricio Siu 388ded9aa5 feat(cloud): add deploy on remote worker 2024-10-03 23:46:26 -06:00
Mauricio Siu 767d3e1944 refactor(cloud): add validation to prevent access to shared resources 2024-10-03 19:50:17 -06:00
Mauricio Siu ec1d6c7430 refactor(cloud): add validation to prevent access to resources from another admin 2024-10-03 19:48:49 -06:00
Mauricio Siu 8abeae5e63 refactor(cloud): validate all the routes to prevent get access from private resource 2024-10-03 19:34:38 -06:00
Mauricio Siu cc90d9ec9b Merge branch 'canary' into feat/cloud 2024-10-03 13:39:06 -06:00
Mauricio Siu 6b5de00fb0 chore: update dev builder command 2024-10-03 12:01:32 -06:00
Mauricio Siu 5ed96fb0ce Merge pull request #523 from lorenzomigliorero/fix/watch-mode
fix: tsx watch flag unwanted reload
2024-10-03 11:01:57 -06:00
Mauricio Siu a73af1d578 Merge pull request #522 from lorenzomigliorero/fix/bitbucket-repositories-length
fix: bitbucket repositories length
2024-10-03 11:00:25 -06:00
Mauricio Siu 5867a27901 Merge pull request #459 from seppulcro/feat/add-roundcube-template
feat: add roundcube template
2024-10-03 10:59:41 -06:00
Lorenzo Migliorero 8f7bffc349 add fragment 2024-10-03 15:32:45 +02:00
Lorenzo Migliorero 21ee22d4f5 add dropdown link 2024-10-03 15:27:48 +02:00
Lorenzo Migliorero fb72132a4b remove watch flag 2024-10-03 14:05:12 +02:00
Lorenzo Migliorero ca904c15d9 remove console.log 2024-10-03 13:55:28 +02:00
Lorenzo Migliorero 682863f83e fix: repo length 2024-10-03 13:54:40 +02:00
Mauricio Siu 3750977f41 Merge pull request #520 from Dokploy/487-private-docker-container-pull-failed-despite-having-docker-registry-configured-in-registry
fix(registry): add option to login the registry in the remote server
2024-10-03 02:02:51 -06:00
Mauricio Siu 9b401059b0 fix(registry): add option to login the registry in the remote server 2024-10-03 01:56:50 -06:00
Mauricio Siu 6a3ef5c860 Merge pull request #519 from Dokploy/514-failing-to-refresh-docker-composeyml-from-github-repo
fix(compose): delete content when is remote server
2024-10-03 01:32:50 -06:00
seppulcro a36518a8f0 fix: set static docker image version 2024-10-03 08:09:21 +01:00
Mauricio Siu a5eb4b0a72 fix(compose): delete content when is remote server 2024-10-03 01:00:35 -06:00
Mauricio Siu b5c0876dd4 Merge pull request #518 from Dokploy/515-non-admin-users-are-not-able-to-set-up-database-backup
fix(destinations): change admin to protected procedure
2024-10-03 00:53:23 -06:00
Mauricio Siu 9745d12ac8 fix(destinations): change admin to protected procedure 2024-10-03 00:48:00 -06:00
Mauricio Siu 5c72e5a452 refactor: filter by adminId 2024-10-03 00:45:46 -06:00
Mauricio Siu 600f4b2106 refactor: apply migration 2024-10-03 00:15:36 -06:00
Mauricio Siu c12d37fe0a refactor: revent 2024-10-03 00:06:11 -06:00
Mauricio Siu bba8d00ba2 refactor: update dockerfile 2024-10-02 23:56:09 -06:00
Mauricio Siu 78665ffbfa refactor(tabs): hide when is cloud version 2024-10-02 23:51:37 -06:00
Mauricio Siu d41c8c70c3 feat: add ssh key 2024-10-02 22:50:01 -06:00
Mauricio Siu f13e5d449c Revert "refactor: stash"
This reverts commit d256998677.
2024-10-02 22:37:14 -06:00
Mauricio Siu d256998677 refactor: stash 2024-10-02 21:55:54 -06:00
Mauricio Siu 4aaf04ce74 Merge pull request #506 from AprilNEA/fix/domin-port-number-convert
Fix port input value becoming NaN
2024-10-02 13:10:17 -06:00
Mauricio Siu ecfca9419a refactor: remove innecessary conversion 2024-10-02 13:02:20 -06:00
AprilNEA dfd6764320 styles: format code with prettier 2024-10-02 18:22:21 +00:00
Mauricio Siu 73bf5274f5 chore(version): bump version 2024-10-01 14:28:11 -06:00
AprilNEA fc38a42587 fix: convert final value 2024-10-01 14:36:45 +00:00
Mauricio Siu 9b255964fe Merge pull request #511 from Dokploy/509-create-compose-modal-remains-open-after-clicking-create
509 create compose modal remains open after clicking create
2024-09-30 21:33:56 -06:00
Mauricio Siu 29f55ca1a0 Merge pull request #496 from missuo/canary
feat: add update option
2024-09-30 15:04:03 -06:00
Mauricio Siu 6a5fb8faff fix(multi-server): show the servers ip instead of the main ip #502 2024-09-30 15:00:32 -06:00
Mauricio Siu 5c225c8d42 fix(modal): close the modal after the creation #509 2024-09-30 15:00:01 -06:00
AprilNEA c1c5fc978b fix: fix number convert when string empty 2024-09-30 08:35:49 +00:00
Mauricio Siu ffd19f591d chore: add package manager 2024-09-30 01:00:53 -06:00
Mauricio Siu 81a41a7f31 refactor: update healtcheck 2024-09-30 00:54:49 -06:00
Mauricio Siu 1c9b704ecc refactor: update redis url 2024-09-30 00:51:07 -06:00
Mauricio Siu edf1fdedf0 refactor: update paths build 2024-09-30 00:47:47 -06:00
Mauricio Siu 8484649071 refactor: update 2024-09-30 00:44:33 -06:00
Mauricio Siu 1e68248611 refactor: update image 2024-09-30 00:41:21 -06:00
Mauricio Siu b3e35c5838 refactor: upate dockerfile 2024-09-30 00:40:43 -06:00
Mauricio Siu ddd4ba8135 refactor: add dockerfile 2024-09-30 00:39:53 -06:00
Mauricio Siu 539544d0de refactor: update 2024-09-30 00:38:22 -06:00
Mauricio Siu 123b5d098b refactor: update install 2024-09-30 00:32:54 -06:00
Mauricio Siu 796a9ca11f refactor: add builder workspace 2024-09-30 00:31:56 -06:00
Mauricio Siu 2872ef3ccb feat(api): add dockerfile api 2024-09-30 00:27:14 -06:00
Mauricio Siu 06a772e344 chore: add dotenv 2024-09-29 22:55:41 -06:00
Mauricio Siu e99666f4c0 fix(esm): add tsc alias 2024-09-29 21:43:25 -06:00
Mauricio Siu bd243d79e2 refactor: remove logs 2024-09-29 21:01:24 -06:00
Vincent Yang 18b4b23f79 feat: add update option for canary and feature tag 2024-09-29 22:45:39 -04:00
Mauricio Siu 071a9d5104 refactor: cleanup dependencies 2024-09-29 20:35:25 -06:00
Mauricio Siu 61ebd1b16e refactor(server): remove files 2024-09-29 19:14:41 -06:00
Mauricio Siu 9836c988a0 refactor(build): update imports 2024-09-29 18:53:32 -06:00
Mauricio Siu 03d7738032 refactor(dockerfile): update dockerfile 2024-09-29 18:49:07 -06:00
Mauricio Siu 98aa474975 refactor(test): update paths and mocks 2024-09-29 18:44:07 -06:00
Mauricio Siu 7bd6b66551 refactor(multi-server): update path imports 2024-09-29 18:04:45 -06:00
Mauricio Siu 0b2b20caeb chore(version): bump version 2024-09-29 16:24:35 -06:00
Mauricio Siu 6cc64b4454 refactior(terminal): add port to server connect 2024-09-29 16:24:09 -06:00
Mauricio Siu 7046d05f63 Merge pull request #499 from Dokploy/fix/update-ports-validation-
fix(multi-server): remove string validation on port
2024-09-29 14:07:00 -06:00
Mauricio Siu cef21ac8b5 fix(multi-server): remove string validation on port 2024-09-29 13:31:38 -06:00
Mauricio Siu 2ae7e562bb refactor(server): remove files 2024-09-29 13:28:24 -06:00
Mauricio Siu e4b998c608 refactor(server): update imports 2024-09-29 11:55:29 -06:00
Mauricio Siu 9b7aacc934 refactor(server): split logic in to packages 2024-09-29 02:28:58 -06:00
Vincent Young 7027f39c48 feat: add update option 2024-09-28 15:06:20 -04:00
Mauricio Siu cb03b153ac Merge pull request #494 from Dokploy/fix/swagger-token
Fix/swagger token
2024-09-28 02:31:39 -06:00
Mauricio Siu e5d7a0cb10 Merge pull request #493 from Dokploy/491-bug-nixpacks-publish-directory-issues-with-multi-level-paths
fix(nixpacks): adjust build path on nixpacks static
2024-09-28 02:30:29 -06:00
Mauricio Siu bf65bc9462 chore(lint): format 2024-09-28 02:25:06 -06:00
Mauricio Siu b48b9765cd fix(nixpacks): adjust build path on nixpacks static 2024-09-28 02:23:30 -06:00
Mauricio Siu 7cce02f74d Merge pull request #489 from Dokploy/docs/488-env-editor-not-support-multiline-variables
docs: add explanation how to use multiline env variables
2024-09-27 22:38:36 -06:00
Ben 3dc3672406 docs: add explanation how to use multiline env variables 2024-09-27 19:30:41 +02:00
Mauricio Siu bbfe095045 chore(version): bump version 2024-09-27 11:23:44 -06:00
Mauricio Siu 65c1001751 fix(swagger): add mising validation 2024-09-27 11:22:52 -06:00
Mauricio Siu 5212bde021 Merge pull request #486 from Dokploy/333-add-flexible-wwwnon-www-redirect-option
#333 add flexible www/non-www redirect option
2024-09-27 00:42:30 -06:00
Ben 9059f42b03 refactor: display certificate select field after https switch field inside add-domain dialogs 2024-09-26 13:53:10 +02:00
Ben 3b9f5d6f5c feat: add presets for add-redirect dialog 2024-09-26 13:14:14 +02:00
seppulcro 0aff344bc0 fix: change tags for roundcube template 2024-09-24 17:27:11 +01:00
seppulcro 4715f34e15 fix: mdx-components formatting with biome 2024-09-24 17:01:24 +01:00
seppulcro 59386ed4b7 fix: Update docs; Fix useMDXComponents for li override: add missing id for correct remark-gfm footnotes functionallity 2024-09-23 16:13:16 +01:00
Mauricio Siu 21dee4abac refactor: update social networks 2024-09-22 20:03:48 -06:00
Mauricio Siu b04c1206e4 refactor(multi-server): update logs 2024-09-22 19:28:19 -06:00
Mauricio Siu 639bc0e8db chore(version): bump version 2024-09-22 19:17:39 -06:00
Mauricio Siu 9a850d388d Merge pull request #453 from mpcref/patch-1
Generate valid appName on changing name
2024-09-22 19:07:09 -06:00
Mauricio Siu 6c5c374139 feat(docs): add multi server docs and troubleshooting 2024-09-22 18:54:32 -06:00
Mauricio Siu 0b05f8b83c Merge pull request #469 from Dokploy/139-multi-server-feature
139 multi server feature
2024-09-22 17:20:26 -06:00
Mauricio Siu 63d5b775e6 refactor(multi-server): add config 2024-09-22 17:18:07 -06:00
Mauricio Siu cb16de63df refactor(multi-server): copy the right value 2024-09-22 17:12:28 -06:00
Mauricio Siu 31a4a0814e refactor: remove logs 2024-09-22 16:51:31 -06:00
Mauricio Siu 14302ed240 refactor: remove imports 2024-09-22 16:49:07 -06:00
Mauricio Siu 31c55f772d refactor(multi-server): remove logs 2024-09-22 16:37:57 -06:00
Mauricio Siu f0f34df13c refactor(multi-server): improve alerts and add instructions to ssh keys 2024-09-22 13:57:13 -06:00
Mauricio Siu 1a877340d3 refactor(multi-server): delete server only if the server doesn't have associated services 2024-09-22 11:56:31 -06:00
Mauricio Siu f7e43fa1c1 chore(migrations): apply single migration 2024-09-22 01:00:19 -06:00
Mauricio Siu 906906102b fix(backups): remove schedule when updating to prevent to create another cron job #452 2024-09-22 00:39:19 -06:00
Mauricio Siu 245a5175a8 refactor(compose): hide enviroment when is docker stack 2024-09-22 00:06:49 -06:00
Mauricio Siu f427014f52 chore(lint): apply lint 2024-09-21 23:49:42 -06:00
Mauricio Siu 0465a71d86 feat(multi-server): enable docker terminal inside of containers 2024-09-21 23:49:00 -06:00
Mauricio Siu 3de8a18ef9 Merge branch 'canary' into 139-multi-server-feature 2024-09-21 22:55:15 -06:00
Mauricio Siu e317d0c808 Merge branch 'canary' into 139-multi-server-feature 2024-09-21 22:52:44 -06:00
Mauricio Siu ff482ffe28 refactor(multi-server): add docker cleanup cron 2024-09-21 22:47:19 -06:00
Mauricio Siu 82588f3e16 refactor(multi-server): add flag to queues 2024-09-21 21:57:53 -06:00
Mauricio Siu 069f1a7b7a refactor(multi-server): add serverIp 2024-09-21 21:17:03 -06:00
Mauricio Siu 807137d3b1 refactor(multi-server): add modals and separate server actions 2024-09-21 20:02:37 -06:00
Mauricio Siu c03c154fc4 feat(multi-server): add docker containers view to servers 2024-09-21 15:16:15 -06:00
Mauricio Siu 698ff9e918 refactor(multi server): rename functions 2024-09-21 02:10:25 -06:00
Mauricio Siu 8bf6a22db8 feat(multi server): add env and toggle dashboard remote 2024-09-21 01:44:31 -06:00
Mauricio Siu 497d45129c feat(multi-server): add actions to the server 2024-09-21 00:06:41 -06:00
Mauricio Siu 0b22b694e6 refactor: add conditional to prevent access to terminal when is not server 2024-09-20 01:33:06 -06:00
Mauricio Siu ee5516bb91 refactor(server): throw error when authentication fails 2024-09-20 00:15:25 -06:00
Mauricio Siu e90b98e629 refactor: add warning when using keyboard interactive 2024-09-19 23:51:40 -06:00
Mauricio Siu ff382d2029 Update installation.mdx 2024-09-19 23:33:36 -06:00
Mauricio Siu 4a37f85a51 chore: update dockerfile 2024-09-19 22:59:14 -06:00
Mauricio Siu 6bdc833413 chore: add unzip and update rclone installation command 2024-09-19 22:56:59 -06:00
Mauricio Siu 17a64a9402 refactor: add conditional to prevent have value in serverId 2024-09-19 22:51:18 -06:00
Mauricio Siu a22b0797b1 refactor(dockerfile): remove sudo 2024-09-19 22:39:28 -06:00
Mauricio Siu f3b351245a chore(ci/cd): add feature tag 2024-09-19 22:34:21 -06:00
Mauricio Siu 0cb74c5fde chore(license): update license 2024-09-19 22:28:29 -06:00
Mauricio Siu 9a828d4966 refactor(linter): format files 2024-09-19 22:25:25 -06:00
Mauricio Siu 4845c1ad5d refactor(multi-server): use rclone for databases backup local and external server 2024-09-19 21:56:45 -06:00
Mauricio Siu 6159786dfe feat(multi-server): add backups remote server 2024-09-19 21:20:25 -06:00
Mauricio Siu b473062f40 Merge pull request #468 from max-degterev/canary
Missing return for entire domain creation transaction
2024-09-19 20:56:46 -06:00
Max Degterev 6c08f33ebb missing transaction return 2024-09-20 01:42:28 +02:00
Mauricio Siu 3ded0d21d0 chore(version): bump version 2024-09-19 15:48:09 -06:00
Mauricio Siu 9ee8fb1894 Merge pull request #465 from max-degterev/canary
Fix domain creation not returning domain data
2024-09-19 15:47:26 -06:00
Max Degterev 72b1600cd4 linter errors 2024-09-19 23:23:47 +02:00
Max Degterev 04a59c5e21 linter errors 2024-09-19 23:21:26 +02:00
Max Degterev c8f990d541 fix domain creation not returning domain data 2024-09-19 23:01:24 +02:00
seppulcro 5b0bf99cbf fix: update roundcube template remove bad chars 2024-09-19 14:40:20 +01:00
seppulcro 8e227a3286 fix: update roundcube template to match spec 2024-09-19 08:37:37 +01:00
Mauricio Siu 54f855e738 refactor(backups): wip postgres remote backups 2024-09-19 00:50:26 -06:00
Mauricio Siu 65a70c09c1 Merge pull request #463 from Dokploy/460-incorrect-environment-database_url
fix(compose): prevent to pass dokploy database url in docker compose …
2024-09-18 23:13:10 -06:00
Mauricio Siu f25d78a87d fix(compose): prevent to pass dokploy database url in docker compose containers #460 2024-09-18 23:06:40 -06:00
Mauricio Siu 79f39db502 refactor(multi-server): wip remote backups 2024-09-18 23:03:54 -06:00
Mauricio Siu a46e7759b2 refactor(multi-server): add rclone to multi server 2024-09-18 00:40:52 -06:00
seppulcro 869e58739f feat: add roundcube template 2024-09-17 19:57:54 +01:00
Mauricio Siu f001a50278 refactor(multi-server): show all the servers 2024-09-17 00:51:49 -06:00
Mauricio Siu 4c3bc8efdc refactor(deployment): set status error when application or compose have troubles when creating deployment log 2024-09-17 00:49:21 -06:00
Mauricio Siu a591e02ffa refactor(multi-server): add deploy templates on different servers 2024-09-17 00:38:27 -06:00
Mauricio Siu abe787593c refactor(multi-server): remove databases and hide monitoring when using deploy on server 2024-09-17 00:05:47 -06:00
Mauricio Siu 9b312cd9d7 refactor(multi-server): adapt paths on server and in dokploy ui 2024-09-16 23:49:24 -06:00
Mauricio Siu c84d39a20f refactor: remove redis 2024-09-16 15:16:51 -06:00
Michiel Crefcoeur 847d6ecab1 add trailing comma's 2024-09-16 22:21:12 +02:00
Michiel Crefcoeur 8f83ecb9ef formatting 2024-09-16 22:20:02 +02:00
Michiel Crefcoeur 2f9448dde9 corrections 2024-09-16 22:13:30 +02:00
Michiel Crefcoeur e1ec0aee69 replaceAll 2024-09-16 10:50:46 -07:00
Michiel Crefcoeur 7f378b12ae and for database 2024-09-16 07:37:23 -07:00
Michiel Crefcoeur fac984d299 same thing for compose 2024-09-16 07:35:59 -07:00
Michiel Crefcoeur 4f3eb7b362 Generate valid appName on changing name
The same can probably also be done at other places. Should probably be solved in a generic way.
2024-09-16 07:25:16 -07:00
Mauricio Siu d8d0b60cb3 refactor(multi-server): replace executeCommand with execAsyncRemote 2024-09-16 00:40:11 -06:00
Mauricio Siu 19295ba746 feat(multi server): add support for drag n drop 2024-09-15 17:05:17 -06:00
Mauricio Siu 0d3c978aad refactor(deployments): improve build error 2024-09-15 14:48:40 -06:00
Mauricio Siu d2c8632c4f refactor(server): add support for multi server 2024-09-15 01:24:55 -06:00
Mauricio Siu 033bf66405 chore: add dokploy dashboard in readme 2024-09-13 13:49:42 -06:00
Mauricio Siu c549ea17d8 refactor(multi-server): add error support 2024-09-13 01:06:46 -06:00
Mauricio Siu c412dabc54 refactor(multi-server): fix deploy on docker compose 2024-09-13 01:03:38 -06:00
Mauricio Siu 0bd0da2ee4 fix(raw): add raw compose remote 2024-09-12 01:44:21 -06:00
Mauricio Siu bf58ae0f0f feat(remote): add docker compose remote 2024-09-12 01:33:33 -06:00
Mauricio Siu a8d714c20d feat: add multi server compose 2024-09-10 01:11:15 -06:00
Mauricio Siu 86f1bf31b8 feat: add reload, stop and start in remote server 2024-09-09 23:46:24 -06:00
Mauricio Siu 95f75fdccb feat: add application and databases external servers 2024-09-09 15:58:58 -06:00
Mauricio Siu 5afe1645a0 feat: add domains in external server 2024-09-09 09:35:02 -06:00
Mauricio Siu cf06162be7 refactor: show logs to each application and database 2024-09-09 00:14:08 -06:00
Mauricio Siu ea5349c844 feat: add logs for each application 2024-09-08 23:11:39 -06:00
Mauricio Siu 6007427a6c feat: add deploy databases to external server 2024-09-08 22:56:21 -06:00
Mauricio Siu 0a889c5db1 feat: deploy compose on external servers 2024-09-08 22:40:42 -06:00
Mauricio Siu 3d60236b36 feat: add redis installation on server and deploy applications on servers 2024-09-08 22:10:24 -06:00
Mauricio Siu 1a34ba175e refactor: simplify docker commands 2024-09-08 01:52:47 -06:00
Mauricio Siu bd0bbdea26 feat: init multi server feature 2024-09-08 01:45:39 -06:00
Ben Bristow f8721d3e04 fix: remove 'networks' section 2024-09-02 21:20:27 +01:00
Ben Bristow a6c7c3b031 fix: directus healthchecks (fix race condition starting up), volumes for uploads & extensions, add secret/db password generation, bump version to 11.0.2 2024-09-02 21:11:03 +01:00
584 changed files with 51688 additions and 8021 deletions
+2 -1
View File
@@ -11,6 +11,7 @@ jobs:
command: |
cp apps/dokploy/.env.production.example .env.production
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
- run:
name: Build and push AMD64 image
command: |
@@ -61,7 +62,7 @@ jobs:
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
echo $VERSION
TAG="latest"
docker manifest create dokploy/dokploy:${TAG} \
dokploy/dokploy:${TAG}-amd64 \
dokploy/dokploy:${TAG}-arm64
Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 248 KiB

+72 -1
View File
@@ -1,4 +1,4 @@
name: Build Docs & Website Docker images
name: Build Docker images
on:
push:
@@ -48,3 +48,74 @@ jobs:
push: true
tags: dokploy/website:latest
platforms: linux/amd64
build-and-push-cloud-image:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile.cloud
push: true
tags: |
siumauricio/cloud:${{ github.ref_name == 'main' && 'main' || 'canary' }}
platforms: linux/amd64
build-and-push-schedule-image:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile.schedule
push: true
tags: |
siumauricio/schedule:${{ github.ref_name == 'main' && 'main' || 'canary' }}
platforms: linux/amd64
build-and-push-server-image:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile.server
push: true
tags: |
siumauricio/server:${{ github.ref_name == 'main' && 'main' || 'canary' }}
platforms: linux/amd64
+3
View File
@@ -18,6 +18,7 @@ jobs:
node-version: 18.18.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm biome ci
- run: pnpm typecheck
@@ -32,6 +33,7 @@ jobs:
node-version: 18.18.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm build
parallel-tests:
@@ -44,4 +46,5 @@ jobs:
node-version: 18.18.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm test
+1
View File
@@ -0,0 +1 @@
npx commitlint --edit "$1"
+6
View File
@@ -0,0 +1,6 @@
// Skip Husky install in production and CI
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
process.exit(0);
}
const husky = (await import("husky")).default;
console.log(husky());
+2
View File
@@ -0,0 +1,2 @@
pnpm run check
git add .
+6
View File
@@ -71,6 +71,12 @@ Run the command that will spin up all the required services and files.
pnpm run dokploy:setup
```
Build the server package (If you make any changes after in the packages/server folder, you need to rebuild and run this command)
```bash
pnpm run server:build
```
Now run the development server.
```bash
+5 -3
View File
@@ -15,7 +15,9 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# Deploy only the dokploy app
ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/dokploy run build
RUN pnpm --filter=./apps/dokploy --prod deploy /prod/dokploy
RUN cp -R /usr/src/app/apps/dokploy/.next /prod/dokploy/.next
@@ -27,7 +29,7 @@ WORKDIR /app
# Set production
ENV NODE_ENV=production
RUN apt-get update && apt-get install -y curl apache2-utils && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/*
# Copy only the necessary files
COPY --from=build /prod/dokploy/.next ./.next
@@ -42,7 +44,7 @@ COPY --from=build /prod/dokploy/node_modules ./node_modules
# Install docker
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh && curl https://rclone.org/install.sh | bash
# Install Nixpacks and tsx
# | VERBOSE=1 VERSION=1.21.0 bash
@@ -55,4 +57,4 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack
EXPOSE 3000
CMD [ "pnpm", "start" ]
CMD [ "pnpm", "start" ]
+52
View File
@@ -0,0 +1,52 @@
FROM node:18-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/dokploy install --frozen-lockfile
# Deploy only the dokploy app
ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/dokploy run build
RUN pnpm --filter=./apps/dokploy --prod deploy /prod/dokploy
RUN cp -R /usr/src/app/apps/dokploy/.next /prod/dokploy/.next
RUN cp -R /usr/src/app/apps/dokploy/dist /prod/dokploy/dist
FROM base AS dokploy
WORKDIR /app
# Set production
ENV NODE_ENV=production
RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/*
# Copy only the necessary files
COPY --from=build /prod/dokploy/.next ./.next
COPY --from=build /prod/dokploy/dist ./dist
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
COPY --from=build /prod/dokploy/public ./public
COPY --from=build /prod/dokploy/package.json ./package.json
COPY --from=build /prod/dokploy/drizzle ./drizzle
COPY --from=build /prod/dokploy/components.json ./components.json
COPY --from=build /prod/dokploy/node_modules ./node_modules
# Install RCLONE
RUN curl https://rclone.org/install.sh | bash
# tsx
RUN pnpm install -g tsx
EXPOSE 3000
CMD [ "pnpm", "start" ]
+36
View File
@@ -0,0 +1,36 @@
FROM node:18-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/schedules install --frozen-lockfile
# Deploy only the dokploy app
ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/schedules run build
RUN pnpm --filter=./apps/schedules --prod deploy /prod/schedules
RUN cp -R /usr/src/app/apps/schedules/dist /prod/schedules/dist
FROM base AS dokploy
WORKDIR /app
# Set production
ENV NODE_ENV=production
# Copy only the necessary files
COPY --from=build /prod/schedules/dist ./dist
COPY --from=build /prod/schedules/package.json ./package.json
COPY --from=build /prod/schedules/node_modules ./node_modules
CMD HOSTNAME=0.0.0.0 && pnpm start
+36
View File
@@ -0,0 +1,36 @@
FROM node:18-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/api install --frozen-lockfile
# Deploy only the dokploy app
ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/api run build
RUN pnpm --filter=./apps/api --prod deploy /prod/api
RUN cp -R /usr/src/app/apps/api/dist /prod/api/dist
FROM base AS dokploy
WORKDIR /app
# Set production
ENV NODE_ENV=production
# Copy only the necessary files
COPY --from=build /prod/api/dist ./dist
COPY --from=build /prod/api/package.json ./package.json
COPY --from=build /prod/api/node_modules ./node_modules
CMD HOSTNAME=0.0.0.0 && pnpm start
+4 -4
View File
@@ -17,10 +17,10 @@ See the License for the specific language governing permissions and limitations
## Additional Terms for Specific Features
The following additional terms apply to the multi-node support and Docker Compose file support features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
The following additional terms apply to the multi-node support, Docker Compose file and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support and Docker Compose file support, will always be free to use in the self-hosted version.
- **Restriction on Resale**: The multi-node support and Docker Compose file support features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
- **Modification Distribution**: Any modifications to the multi-node support and Docker Compose file support features must be distributed freely and cannot be sold or offered as a service.
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support and Multi Server, will always be free to use in the self-hosted version.
- **Restriction on Resale**: The multi-node support, Docker Compose file support and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support and Multi Server features must be distributed freely and cannot be sold or offered as a service.
For further inquiries or permissions, please contact us directly.
+11 -2
View File
@@ -30,8 +30,9 @@ Dokploy include multiples features to make your life easier.
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
- **Docker Management**: Easily deploy and manage Docker containers.
- **CLI/API**: Manage your applications and databases using the command line or trought the API.
- **CLI/API**: Manage your applications and databases using the command line or through the API.
- **Notifications**: Get notified when your deployments are successful or failed (Slack, Discord, Telegram, Email, etc.)
- **Multi Server**: Deploy and manager your applications remotely to external servers.
- **Self-Hosted**: Self-host Dokploy on your VPS.
## 🚀 Getting Started
@@ -58,7 +59,14 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
### Hero Sponsors 🎖
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" ><img src=".github/sponsors/hostinger.jpg" alt="Hostinger" width="200"/></a>
<div style="display: flex; align-items: center; gap: 20px;">
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" style="display: inline-block;">
<img src=".github/sponsors/hostinger.jpg" alt="Hostinger" height="50"/>
</a>
<a href="https://www.lxaer.com/?ref=dokploy" target="_blank" style="display: inline-block;">
<img src=".github/sponsors/lxaer.png" alt="LX Aer" height="50"/>
</a>
</div>
### Premium Supporters 🥇
@@ -81,6 +89,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://steamsets.com/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
<a href="https://rivo.gg/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/126797452?s=200&v=4" width="60px" alt="Rivo.gg"/></a>
</div>
#### Organizations:
+22 -4
View File
@@ -1,15 +1,33 @@
{
"name": "my-app",
"name": "@dokploy/api",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts"
"dev": "PORT=4000 tsx watch src/index.ts",
"build": "tsc --project tsconfig.json",
"start": "node --experimental-specifier-resolution=node dist/index.js",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"pino": "9.4.0",
"pino-pretty": "11.2.2",
"@hono/zod-validator": "0.3.0",
"zod": "^3.23.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"@dokploy/server": "workspace:*",
"@hono/node-server": "^1.12.1",
"hono": "^4.5.8",
"dotenv": "^16.3.1"
"dotenv": "^16.3.1",
"redis": "4.7.0",
"@nerimity/mimiqueue": "1.2.3"
},
"devDependencies": {
"typescript": "^5.4.2",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/node": "^20.11.17",
"tsx": "^4.7.1"
}
},
"packageManager": "pnpm@9.5.0"
}
+49 -54
View File
@@ -1,66 +1,61 @@
import { serve } from "@hono/node-server";
import { config } from "dotenv";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { validateLemonSqueezyLicense } from "./utils";
config();
import "dotenv/config";
import { zValidator } from "@hono/zod-validator";
import { Queue } from "@nerimity/mimiqueue";
import { createClient } from "redis";
import { logger } from "./logger";
import { type DeployJob, deployJobSchema } from "./schema";
import { deploy } from "./utils";
const app = new Hono();
app.use(
"/*",
cors({
origin: ["http://localhost:3000", "http://localhost:3001"], // Ajusta esto a los orígenes de tu aplicación Next.js
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization"],
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
maxAge: 600,
credentials: true,
}),
);
export const LEMON_SQUEEZY_API_KEY = process.env.LEMON_SQUEEZY_API_KEY;
export const LEMON_SQUEEZY_STORE_ID = process.env.LEMON_SQUEEZY_STORE_ID;
app.get("/v1/health", (c) => {
return c.text("Hello Hono!");
const redisClient = createClient({
url: process.env.REDIS_URL,
});
app.post("/v1/validate-license", async (c) => {
const { licenseKey } = await c.req.json();
app.use(async (c, next) => {
if (c.req.path === "/health") {
return next();
}
const authHeader = c.req.header("X-API-Key");
if (!licenseKey) {
return c.json({ error: "License key is required" }, 400);
if (process.env.API_KEY !== authHeader) {
return c.json({ message: "Invalid API Key" }, 403);
}
try {
const licenseValidation = await validateLemonSqueezyLicense(licenseKey);
if (licenseValidation.valid) {
return c.json({
valid: true,
message: "License is valid",
metadata: licenseValidation.meta,
});
}
return c.json(
{
valid: false,
message: licenseValidation.error || "Invalid license",
},
400,
);
} catch (error) {
console.error("Error during license validation:", error);
return c.json({ error: "Internal server error" }, 500);
}
return next();
});
const port = 4000;
console.log(`Server is running on port ${port}`);
serve({
fetch: app.fetch,
port,
app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
const data = c.req.valid("json");
const res = queue.add(data, { groupName: data.serverId });
return c.json(
{
message: "Deployment Added",
},
200,
);
});
app.get("/health", async (c) => {
return c.json({ status: "ok" });
});
const queue = new Queue({
name: "deployments",
process: async (job: DeployJob) => {
logger.info("Deploying job", job);
return await deploy(job);
},
redisClient,
});
(async () => {
await redisClient.connect();
await redisClient.flushAll();
logger.info("Redis Cleaned");
})();
const port = Number.parseInt(process.env.PORT || "3000");
logger.info("Starting Deployments Server ✅", port);
serve({ fetch: app.fetch, port });
+10
View File
@@ -0,0 +1,10 @@
import pino from "pino";
export const logger = pino({
transport: {
target: "pino-pretty",
options: {
colorize: true,
},
},
});
+24
View File
@@ -0,0 +1,24 @@
import { z } from "zod";
export const deployJobSchema = z.discriminatedUnion("applicationType", [
z.object({
applicationId: z.string(),
titleLog: z.string(),
descriptionLog: z.string(),
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("application"),
serverId: z.string().min(1),
}),
z.object({
composeId: z.string(),
titleLog: z.string(),
descriptionLog: z.string(),
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("compose"),
serverId: z.string().min(1),
}),
]);
export type DeployJob = z.infer<typeof deployJobSchema>;
+90 -22
View File
@@ -1,28 +1,96 @@
import { LEMON_SQUEEZY_API_KEY, LEMON_SQUEEZY_STORE_ID } from ".";
import {
deployApplication,
deployCompose,
deployRemoteApplication,
deployRemoteCompose,
rebuildApplication,
rebuildCompose,
rebuildRemoteApplication,
rebuildRemoteCompose,
updateApplicationStatus,
updateCompose,
} from "@dokploy/server";
import type { DeployJob } from "./schema";
import type { LemonSqueezyLicenseResponse } from "./types";
export const validateLemonSqueezyLicense = async (
licenseKey: string,
): Promise<LemonSqueezyLicenseResponse> => {
try {
const response = await fetch(
"https://api.lemonsqueezy.com/v1/licenses/validate",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": LEMON_SQUEEZY_API_KEY as string,
},
body: JSON.stringify({
license_key: licenseKey,
store_id: LEMON_SQUEEZY_STORE_ID as string,
}),
},
);
// const LEMON_SQUEEZY_API_KEY = process.env.LEMON_SQUEEZY_API_KEY;
// const LEMON_SQUEEZY_STORE_ID = process.env.LEMON_SQUEEZY_STORE_ID;
// export const validateLemonSqueezyLicense = async (
// licenseKey: string,
// ): Promise<LemonSqueezyLicenseResponse> => {
// try {
// const response = await fetch(
// "https://api.lemonsqueezy.com/v1/licenses/validate",
// {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// "x-api-key": LEMON_SQUEEZY_API_KEY as string,
// },
// body: JSON.stringify({
// license_key: licenseKey,
// store_id: LEMON_SQUEEZY_STORE_ID as string,
// }),
// },
// );
return response.json();
// return response.json();
// } catch (error) {
// console.error("Error validating license:", error);
// return { valid: false, error: "Error validating license" };
// }
// };
export const deploy = async (job: DeployJob) => {
try {
if (job.applicationType === "application") {
await updateApplicationStatus(job.applicationId, "running");
if (job.server) {
if (job.type === "redeploy") {
await rebuildRemoteApplication({
applicationId: job.applicationId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
});
} else if (job.type === "deploy") {
await deployRemoteApplication({
applicationId: job.applicationId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
});
}
}
} else if (job.applicationType === "compose") {
await updateCompose(job.composeId, {
composeStatus: "running",
});
if (job.server) {
if (job.type === "redeploy") {
await rebuildRemoteCompose({
composeId: job.composeId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
});
} else if (job.type === "deploy") {
await deployRemoteCompose({
composeId: job.composeId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
});
}
}
}
} catch (error) {
console.error("Error validating license:", error);
return { valid: false, error: "Error validating license" };
console.log(error);
if (job.applicationType === "application") {
await updateApplicationStatus(job.applicationId, "error");
} else if (job.applicationType === "compose") {
await updateCompose(job.composeId, {
composeStatus: "error",
});
}
}
return true;
};
+4 -3
View File
@@ -2,11 +2,12 @@
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"moduleResolution": "Node",
"strict": true,
"skipLibCheck": true,
"types": ["node"],
"outDir": "dist",
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
},
"exclude": ["node_modules", "dist"]
}
+1 -1
View File
@@ -74,7 +74,7 @@ export function generateMetadata({
},
twitter: {
card: "summary_large_image",
creator: "@siumauricio",
creator: "@getdokploy",
title: page.data.title,
description: page.data.description,
images: [
@@ -15,6 +15,8 @@ Configure the source of your code, the way your application is built, and also m
If you need to assign environment variables to your application, you can do so here.
In case you need to use a multiline variable, you can wrap it in double quotes just like this `'"here_is_my_private_key"'`.
## Monitoring
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
@@ -26,6 +26,8 @@ Actions like deploying, updating, and deleting your database, and stopping it.
If you need to assign environment variables to your application, you can do so here.
In case you need to use a multiline variable, you can wrap it in double quotes just like this `'"here_is_my_private_key"'`.
## Monitoring
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
@@ -1,22 +1,23 @@
---
title: 'Comparison'
description: 'A comparison of Dokploy, CapRover, Dokku, and Coolify'
title: "Comparison"
description: "A comparison of Dokploy, CapRover, Dokku, and Coolify"
---
Comparison of the following deployment tools:
| Feature | Dokploy | CapRover | Dokku | Coolify |
|-----------------------------------|---------------------------------------|--------------------------------------|--------------------------------------|--------------------------------------|
| **User Interface** | ✅ | ✅ | ❌ | ✅ |
| **Docker compose support** | | ❌ | ❌ | ✅ |
| **API/CLI** | ✅ | ✅ | ✅ | ✅ |
| **Multi node support** | ✅ | ✅ | ❌ | ✅ |
| **Traefik Integration** | ✅ | ✅ | Available via Plugins | ✅ |
| **User Permission Management** | ✅ | ❌ | ❌ | ✅ |
| **Advanced User Permission Management** | ✅ | ❌ | ❌ | ❌ |
| **Terminal Access Built In** | ✅ | ❌ | ❌ | ✅ |
| **Database Support** | ✅ | ✅ | ❌ | ✅ |
| **Monitoring** | ✅ | ✅ | ❌ | ❌ |
| **Backups** | ✅ | Available via Plugins | Available via Plugins | ✅ |
| **Open Source** | ✅ | ✅ | ✅ | ✅ |
| **Cloud/Paid Version** | ❌ | ✅ | ❌ | ✅ |
| Feature | Dokploy | CapRover | Dokku | Coolify |
| --------------------------------------- | ------- | --------------------- | --------------------- | ------- |
| **User Interface** | ✅ | ✅ | ❌ | ✅ |
| **Docker compose support** || ❌ | ❌ | ✅ |
| **API/CLI** | ✅ | ✅ | ✅ | ✅ |
| **Multi node support** | ✅ | ✅ | ❌ | ✅ |
| **Traefik Integration** | ✅ | ✅ | Available via Plugins | ✅ |
| **User Permission Management** | ✅ | ❌ | ❌ | ✅ |
| **Advanced User Permission Management** | ✅ | ❌ | ❌ | ❌ |
| **Terminal Access Built In** | ✅ | ❌ | ❌ | ✅ |
| **Database Support** | ✅ | ✅ | ❌ | ✅ |
| **Monitoring** | ✅ | ✅ | ❌ | ❌ |
| **Backups** | ✅ | Available via Plugins | Available via Plugins | ✅ |
| **Open Source** | ✅ | ✅ | ✅ | ✅ |
| **Multi Server Support** | ✅ | ❌ | ❌ | ✅ |
| **Cloud/Paid Version** | ❌ | ✅ | ✅ | ✅ |
@@ -29,7 +29,7 @@ We have tested on the following Linux Distros:
### Providers
- [Hostinger](https://www.hostinger.com/vps-hosting?ref=dokploy) Get 20% Discount using this referral link: [Referral Link](https://hostinger.com?REFERRALCODE=1SIUMAURICI97)
- [Hostinger](https://www.hostinger.com/vps-hosting?ref=dokploy) Get 20% Discount using this referral link: [Referral Link](https://www.hostinger.com/vps-hosting?REFERRALCODE=1SIUMAURICI97)
- [DigitalOcean](https://www.digitalocean.com/pricing/droplets#basic-droplets) Get 200$ credits for free with this referral link: [Referral Link](https://m.do.co/c/db24efd43f35)
- [Hetzner](https://www.hetzner.com/cloud/) Get 20€ credits for free with this referral link: [Referral Link](https://hetzner.cloud/?ref=vou4fhxJ1W2D)
- [Linode](https://www.linode.com/es/pricing/#compute-shared)
+3
View File
@@ -64,6 +64,9 @@
"docker/overview",
"---Monitoring---",
"monitoring/overview",
"---Multi Server---",
"multi-server/overview",
"multi-server/example",
"---Cluster---",
"cluster/overview",
"---Deployments---",
@@ -0,0 +1,117 @@
---
title: Example
description: "Example to setup a remote server and deploy application in a VPS."
---
import { Callout } from "fumadocs-ui/components/callout";
Multi server allows you to deploy your apps remotely to different servers without needing to build and run them where the Dokploy UI is installed.
## Requirements
1. To install Dokploy UI, follow the [installation guide](en/docs/core/get-started/installation).
2. Create an SSH key by going to `/dashboard/settings/ssh-keys` and add a new key. Be sure to copy the public key.
<ImageZoom
src="/assets/ssh-keys.png"
alt="Architecture Diagram"
width={1000}
height={600}
className="rounded-lg"
/>
3. Decide which remote server to deploy your apps on. We recommend these reliable providers:
- [Hostinger](https://www.hostinger.com/vps-hosting?ref=dokploy) Get 20% off with this [referral link](https://www.hostinger.com/vps-hosting?REFERRALCODE=1SIUMAURICI97).
- [DigitalOcean](https://www.digitalocean.com/pricing/droplets#basic-droplets) Get $200 credits for free with this [referral link](https://m.do.co/c/db24efd43f35).
- [Hetzner](https://www.hetzner.com/cloud/) Get €20 credits with this [referral link](https://hetzner.cloud/?ref=vou4fhxJ1W2D).
- [Linode](https://www.linode.com/es/pricing/#compute-shared).
- [Vultr](https://www.vultr.com/pricing/#cloud-compute).
- [Scaleway](https://www.scaleway.com/en/pricing/?tags=baremetal,available).
- [Google Cloud](https://cloud.google.com/).
- [AWS](https://aws.amazon.com/ec2/pricing/).
4. When creating the server, it should ask for SSH keys. Ideally, use your computer's public key and the key you generated in the previous step. Here's how to add the public key in Hostinger:
<ImageZoom
src="/assets/hostinger-add-sshkey.png"
alt="Adding SSH key"
width={1000}
height={600}
className="rounded-lg"
/>
<Callout>The steps are similar across other providers.</Callout>
5. Copy the servers IP address and ensure you know the username (often `root`). Fill in all fields and click `Create`.
<ImageZoom
src="/assets/multi-server-add-server.png"
alt="Add server"
width={1000}
height={600}
className="rounded-lg"
/>
6. To test connectivity, open the server dropdown and click `Enter Terminal`. If everything is correct, you should be able to interact with the remote server.
7. Click `Setup Server` to proceed. There are two tabs: SSH Keys and Deployments. This guide explains the easy way, but you can follow the manual process via the Dokploy UI if you prefer.
<ImageZoom
src="/assets/multi-server-setup-2.png"
alt="Setup process"
width={1000}
height={600}
className="rounded-lg"
/>
8. Click `Deployments`, then `Setup Server`. If everything is correct, you should see output similar to this:
<ImageZoom
src="/assets/multi-server-setup-3.png"
alt="Server setup output"
width={1000}
height={600}
className="rounded-lg"
/>
<Callout>
You only need to run this setup once. If Dokploy updates later, check the
release notes to see if rerunning this command is required.
</Callout>
9. You're ready to deploy your apps! Let's test it out:
<ImageZoom
src="/assets/multi-server-add-app.png"
alt="Add app"
width={1000}
height={600}
className="rounded-lg"
/>
10. To check which server an app belongs to, youll see the server name at the top. If no server is selected, it defaults to `Dokploy Server`. Click `Deploy` to start building your app on the remote server. You can check the `Logs` tab to see the build process. For this example, well use a test repo:
Repo: `https://github.com/Dokploy/examples.git`
Branch: `main`
Build Path: `/astro`
<ImageZoom
src="/assets/multi-server-setup-app.png"
alt="App setup"
width={1000}
height={600}
className="rounded-lg"
/>
11. Once the build is done, go to `Domains` and create a free domain. Just click `Create` and youre good to go! 🎊
{" "}
<ImageZoom
src="/assets/multi-server-finish.png"
alt="Finished setup"
width={1000}
height={600}
className="rounded-lg"
/>
@@ -0,0 +1,29 @@
---
title: Overview
description: "Deploy your apps to multiple servers remotely."
---
import { Callout } from "fumadocs-ui/components/callout";
Multi server allows you to deploy your apps remotely to different servers without needing to build and run them where the Dokploy UI is installed.
To use the multi-server feature, you need to have Dokploy UI installed either locally or on a remote server. We recommend using a remote server for better connectivity, security, and isolation, for remote instances we install only a traefik instance.
If you plan to only deploy apps to remote servers and use Dokploy UI for managing deployments, Dokploy will use around 250 MB of RAM and minimal CPU, so a low-resource server should be sufficient.
All the features we have documented previously are supported by Dokploy Multi Server. The only feature not supported is remote server monitoring, due to performance reasons. However, all functionalities should work the same as when deploying on the same server where Dokploy UI is installed.
## Features
1. **Enter the terminal**: Allows you to access the terminal of the remote server.
2. **Setup Server**: Allows you to configure the remote server.
- **SSH Keys**: Steps to add SSH keys to the remote server.
- **Deployments**: Steps to configure the remote server for deploying applications.
3. **Edit Server**: Allows you to modify the remote server's details, such as SSH key, name, description, IP, etc.
4. **View Actions**: Lets you perform actions like managing the Traefik instance, storage, and activating Docker cleanup.
5. **Show Traefik File System**: Displays the contents of the remote server's directory.
6. **Show Docker Containers**: Shows the Docker containers running on the remote server.
<Callout>
Remote server monitoring is not supported due to performance reasons.
</Callout>
@@ -31,8 +31,7 @@ The following templates are available:
- **Wordpress**: Open Source Content Management System
- **Open WebUI**: Free and Open Source ChatGPT Alternative
- **Teable**: Open Source Airtable Alternative, Developer Friendly, No-code Database Built on Postgres
- **Roundcube**: Free and open source webmail software for the masses, written in PHP, uses SMTP[^1].
## Create your own template
@@ -41,3 +40,5 @@ We accept contributions to upload new templates to the dokploy repository.
Make sure to follow the guidelines for creating a template:
[Steps to create your own template](https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#templates)
[^1]: Please note that if you're self-hosting a mail server you need port 25 to be open for SMTP (Mail Transmission Protocol that allows you to send and receive) to work properly. Some VPS providers like [Hetzner](https://docs.hetzner.com/cloud/servers/faq/#why-can-i-not-send-any-mails-from-my-server) block this port by default for new clients.
@@ -3,4 +3,90 @@ title: Overview
description: Solve the most common problems that occur when using Dokploy.
---
WIP
## Applications Domain Not Working?
You see the deployment succeeded, and logs are running, but the domain isn't working? Here's what to check:
1. **Correct Port Mapping**: Ensure the domain is using the correct port for your application. For example, if you're using Next.js, the port should be `3000`, or for Laravel, it should be `8000`. If you change the app port, update the domain to reflect that.
2. **Avoid Using `Ports` in Advanced Settings**: Generally, there's no need to use the `Ports` feature unless you want to access your app via `IP:port`. Leaving this feature enabled may interfere with your domain.
3. **Let's Encrypt Certificates**: It's crucial to point the domain to your servers IP **before** adding it in Dokploy. If the domain is added first, the certificate wont be generated, and you may need to recreate the domain or restart Traefik.
4. **Listen on 0.0.0.0, Not 127.0.0.1**: If your app is bound to `127.0.0.1` (which is common in Vite apps), switch it to `0.0.0.0` to allow external access.
## Logs and Monitoring Not Working After Changing Application Placement?
This is expected behavior. If the application is running on a different node (worker), the UI wont have access to logs or monitoring, as they're not on the same node.
## Mounts Are Causing My Application Not to Run?
Docker Swarm won't run your application if there are invalid mounts, even if the deployment shows as successful. Double-check your mounts to ensure they are valid.
## Volumes in Docker Compose Not Working?
For Docker Compose, all file mounts defined in the `volumes` section will be stored in the `files` folder. This is the default directory structure:
## I added a volume to my docker compose, but is not finding the volume?
For docker compose all the file mounts you've created in the volumes section will be stored to files folder, this is the default structure of the docker compose.
```
/application-name
/code
/files
```
So instead of using this invalid way to mount a volume:
```yaml
volumes:
- "/folder:/path/in/container" ❌
```
You should use this format:
```yaml
volumes:
- "../files/my-database:/var/lib/mysql" ✅
- "../files/my-configs:/etc/my-app/config" ✅
```
## Logs Not Loading When Deploying to a Remote Server?
There are a few potential reasons for this:
1. **Slow Server:**: If the server is too slow, it may struggle to handle concurrent requests, leading to SSL handshake errors.
2. **Insufficient Disk Space:** If the server doesn't have enough disk space, the logs may not load.
## Docker Compose Domain Not Working?
When adding a domain in your Docker Compose file, its not necessary to expose the ports directly. Simply specify the port where your app is running. Exposing the ports can lead to conflicts with other applications or ports.
Example of what not to do:
```yaml
services:
app:
image: dokploy/dokploy:latest
ports:
- 3000:3000
```
Recommended approach:
```yaml
services:
app:
image: dokploy/dokploy:latest
ports:
- 3000
- 80
```
Then, when creating the domain in Dokploy, specify the service name and port, like this:
```yaml
domain: my-app.com
serviceName: app
port: 3000
```
+4 -2
View File
@@ -10,8 +10,10 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
p: ({ children }) => (
<p className="text-[#3E4342] dark:text-muted-foreground">{children}</p>
),
li: ({ children }) => (
<li className="text-[#3E4342] dark:text-muted-foreground">{children}</li>
li: ({ children, id }) => (
<li {...{ id }} className="text-[#3E4342] dark:text-muted-foreground">
{children}
</li>
),
};
}
+2 -6
View File
@@ -21,15 +21,11 @@
"react-ga4": "^2.1.0"
},
"devDependencies": {
"tsx": "4.15.7",
"@biomejs/biome": "1.8.1",
"autoprefixer": "10.4.12",
"@types/mdx": "^2.0.13",
"@types/node": "^20.14.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.5"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

+4 -4
View File
@@ -17,10 +17,10 @@ See the License for the specific language governing permissions and limitations
## Additional Terms for Specific Features
The following additional terms apply to the multi-node support and Docker Compose file support features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
The following additional terms apply to the multi-node support, Docker Compose file and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support and Docker Compose file support, will always be free to use in the self-hosted version.
- **Restriction on Resale**: The multi-node support and Docker Compose file support features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
- **Modification Distribution**: Any modifications to the multi-node support and Docker Compose file support features must be distributed freely and cannot be sold or offered as a service.
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support and Multi Server, will always be free to use in the self-hosted version.
- **Restriction on Resale**: The multi-node support, Docker Compose file support and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support and Multi Server features must be distributed freely and cannot be sold or offered as a service.
For further inquiries or permissions, please contact us directly.
@@ -1,5 +1,5 @@
import { addSuffixToAllProperties } from "@/server/utils/docker/compose";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { addSuffixToAllProperties } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToConfigsRoot } from "@/server/utils/docker/compose/configs";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToConfigsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToConfigsInServices } from "@/server/utils/docker/compose/configs";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToConfigsInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,9 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import {
addSuffixToAllConfigs,
addSuffixToConfigsRoot,
} from "@/server/utils/docker/compose/configs";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToAllConfigs, addSuffixToConfigsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,5 +1,5 @@
import type { Domain } from "@/server/api/services/domain";
import { createDomainLabels } from "@/server/utils/docker/domain";
import type { Domain } from "@dokploy/server";
import { createDomainLabels } from "@dokploy/server";
import { describe, expect, it } from "vitest";
describe("createDomainLabels", () => {
@@ -1,4 +1,4 @@
import { addDokployNetworkToRoot } from "@/server/utils/docker/domain";
import { addDokployNetworkToRoot } from "@dokploy/server";
import { describe, expect, it } from "vitest";
describe("addDokployNetworkToRoot", () => {
@@ -1,4 +1,4 @@
import { addDokployNetworkToService } from "@/server/utils/docker/domain";
import { addDokployNetworkToService } from "@dokploy/server";
import { describe, expect, it } from "vitest";
describe("addDokployNetworkToService", () => {
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToNetworksRoot } from "@/server/utils/docker/compose/network";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToNetworksRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToServiceNetworks } from "@/server/utils/docker/compose/network";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNetworks } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,10 +1,10 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { generateRandomHash } from "@dokploy/server";
import {
addSuffixToAllNetworks,
addSuffixToServiceNetworks,
} from "@/server/utils/docker/compose/network";
import { addSuffixToNetworksRoot } from "@/server/utils/docker/compose/network";
import type { ComposeSpecification } from "@/server/utils/docker/types";
} from "@dokploy/server";
import { addSuffixToNetworksRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToSecretsRoot } from "@/server/utils/docker/compose/secrets";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToSecretsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { dump, load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToSecretsInServices } from "@/server/utils/docker/compose/secrets";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToSecretsInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,5 +1,5 @@
import { addSuffixToAllSecrets } from "@/server/utils/docker/compose/secrets";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { addSuffixToAllSecrets } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,8 +1,8 @@
import {
addSuffixToAllServiceNames,
addSuffixToServiceNames,
} from "@/server/utils/docker/compose/service";
import type { ComposeSpecification } from "@/server/utils/docker/types";
} from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,9 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import {
addSuffixToAllVolumes,
addSuffixToVolumesRoot,
} from "@/server/utils/docker/compose/volume";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToAllVolumes, addSuffixToVolumesRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToVolumesRoot } from "@/server/utils/docker/compose/volume";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToVolumesRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,6 +1,6 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { addSuffixToVolumesInServices } from "@/server/utils/docker/compose/volume";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToVolumesInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
@@ -1,9 +1,9 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import { generateRandomHash } from "@dokploy/server";
import {
addSuffixToAllVolumes,
addSuffixToVolumesInServices,
} from "@/server/utils/docker/compose/volume";
import type { ComposeSpecification } from "@/server/utils/docker/types";
} from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
+114 -27
View File
@@ -1,7 +1,9 @@
import fs from "node:fs/promises";
import path from "node:path";
import { APPLICATIONS_PATH } from "@/server/constants";
import { unzipDrop } from "@/server/utils/builders/drop";
import { paths } from "@dokploy/server/dist/constants";
const { APPLICATIONS_PATH } = paths();
import type { ApplicationNested } from "@dokploy/server";
import { unzipDrop } from "@dokploy/server";
import AdmZip from "adm-zip";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
@@ -11,11 +13,87 @@ if (typeof window === "undefined") {
globalThis.FileList = undici.FileList as any;
}
vi.mock("@/server/constants", () => ({
APPLICATIONS_PATH: "./__test__/drop/zips/output",
}));
const baseApp: ApplicationNested = {
applicationId: "",
applicationStatus: "done",
appName: "",
autoDeploy: true,
serverId: "",
branch: null,
dockerBuildStage: "",
buildArgs: null,
buildPath: "/",
gitlabPathNamespace: "",
buildType: "nixpacks",
bitbucketBranch: "",
bitbucketBuildPath: "",
bitbucketId: "",
bitbucketRepository: "",
bitbucketOwner: "",
githubId: "",
gitlabProjectId: 0,
gitlabBranch: "",
gitlabBuildPath: "",
gitlabId: "",
gitlabRepository: "",
gitlabOwner: "",
command: null,
cpuLimit: null,
cpuReservation: null,
createdAt: "",
customGitBranch: "",
customGitBuildPath: "",
customGitSSHKeyId: null,
customGitUrl: "",
description: "",
dockerfile: null,
dockerImage: null,
dropBuildPath: null,
enabled: null,
env: null,
healthCheckSwarm: null,
labelsSwarm: null,
memoryLimit: null,
memoryReservation: null,
modeSwarm: null,
mounts: [],
name: "",
networkSwarm: null,
owner: null,
password: null,
placementSwarm: null,
ports: [],
projectId: "",
publishDirectory: null,
redirects: [],
refreshToken: "",
registry: null,
registryId: null,
replicas: 1,
repository: null,
restartPolicySwarm: null,
rollbackConfigSwarm: null,
security: [],
sourceType: "git",
subtitle: null,
title: null,
updateConfigSwarm: null,
username: null,
dockerContextPath: null,
};
vi.mock("@dokploy/server/dist/constants", async (importOriginal) => {
const actual = await importOriginal();
return {
// @ts-ignore
...actual,
paths: () => ({
APPLICATIONS_PATH: "./__test__/drop/zips/output",
}),
};
});
describe("unzipDrop using real zip files", () => {
// const { APPLICATIONS_PATH } = paths();
beforeAll(async () => {
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
});
@@ -25,39 +103,46 @@ describe("unzipDrop using real zip files", () => {
});
it("should correctly extract a zip with a single root folder", async () => {
const appName = "single-file";
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
await unzipDrop(file, appName);
const files = await fs.readdir(outputPath, { withFileTypes: true });
expect(files.some((f) => f.name === "test.txt")).toBe(true);
baseApp.appName = "single-file";
// const appName = "single-file";
try {
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
console.log(`Output Path: ${outputPath}`);
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
expect(files.some((f) => f.name === "test.txt")).toBe(true);
} catch (err) {
console.log(err);
} finally {
}
});
it("should correctly extract a zip with a single root folder and a subfolder", async () => {
const appName = "folderwithfile";
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
baseApp.appName = "folderwithfile";
// const appName = "folderwithfile";
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/folder-with-file.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
await unzipDrop(file, appName);
await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
expect(files.some((f) => f.name === "folder1.txt")).toBe(true);
});
it("should correctly extract a zip with multiple root folders", async () => {
const appName = "two-folders";
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
baseApp.appName = "two-folders";
// const appName = "two-folders";
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/two-folders.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
await unzipDrop(file, appName);
await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
@@ -66,13 +151,14 @@ describe("unzipDrop using real zip files", () => {
});
it("should correctly extract a zip with a single root with a file", async () => {
const appName = "nested";
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
baseApp.appName = "nested";
// const appName = "nested";
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/nested.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
await unzipDrop(file, appName);
await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
@@ -82,13 +168,14 @@ describe("unzipDrop using real zip files", () => {
});
it("should correctly extract a zip with a single root with a folder", async () => {
const appName = "folder-with-sibling-file";
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
baseApp.appName = "folder-with-sibling-file";
// const appName = "folder-with-sibling-file";
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/folder-with-sibling-file.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
await unzipDrop(file, appName);
await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
@@ -1,4 +1,4 @@
import { parseRawConfig, processLogs } from "@/server/utils/access-log/utils";
import { parseRawConfig, processLogs } from "@dokploy/server";
import { describe, expect, it } from "vitest";
const sampleLogEntry = `{"ClientAddr":"172.19.0.1:56732","ClientHost":"172.19.0.1","ClientPort":"56732","ClientUsername":"-","DownstreamContentSize":0,"DownstreamStatus":304,"Duration":14729375,"OriginContentSize":0,"OriginDuration":14051833,"OriginStatus":304,"Overhead":677542,"RequestAddr":"s222-umami-c381af.traefik.me","RequestContentSize":0,"RequestCount":122,"RequestHost":"s222-umami-c381af.traefik.me","RequestMethod":"GET","RequestPath":"/dashboard?_rsc=1rugv","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-umami-60e104-47-web@docker","ServiceAddr":"10.0.1.15:3000","ServiceName":"s222-umami-60e104-47-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.15:3000","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T04:34:37.306691884Z","StartUTC":"2024-08-25T04:34:37.306691884Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T04:34:37Z"}`;
@@ -5,11 +5,12 @@ vi.mock("node:fs", () => ({
default: fs,
}));
import type { Admin } from "@/server/api/services/admin";
import { createDefaultServerTraefikConfig } from "@/server/setup/traefik-setup";
import { loadOrCreateConfig } from "@/server/utils/traefik/application";
import type { FileConfig } from "@/server/utils/traefik/file-types";
import { updateServerTraefik } from "@/server/utils/traefik/web-server";
import type { Admin, FileConfig } from "@dokploy/server";
import {
createDefaultServerTraefikConfig,
loadOrCreateConfig,
updateServerTraefik,
} from "@dokploy/server";
import { beforeEach, expect, test, vi } from "vitest";
const baseAdmin: Admin = {
@@ -23,6 +24,9 @@ const baseAdmin: Admin = {
sshPrivateKey: null,
enableDockerCleanup: false,
enableLogRotation: false,
serversQuantity: 0,
stripeCustomerId: "",
stripeSubscriptionId: "",
};
beforeEach(() => {
@@ -1,7 +1,7 @@
import type { Domain } from "@/server/api/services/domain";
import type { Redirect } from "@/server/api/services/redirect";
import type { ApplicationNested } from "@/server/utils/builders";
import { createRouterConfig } from "@/server/utils/traefik/domain";
import type { Domain } from "@dokploy/server";
import type { Redirect } from "@dokploy/server";
import type { ApplicationNested } from "@dokploy/server";
import { createRouterConfig } from "@dokploy/server";
import { expect, test } from "vitest";
const baseApp: ApplicationNested = {
@@ -9,6 +9,7 @@ const baseApp: ApplicationNested = {
applicationStatus: "done",
appName: "",
autoDeploy: true,
serverId: "",
branch: null,
dockerBuildStage: "",
buildArgs: null,
+5
View File
@@ -13,4 +13,9 @@ export default defineConfig({
exclude: ["**/node_modules/**", "**/dist/**", "**/.docker/**"],
pool: "forks",
},
define: {
"process.env": {
NODE: "test",
},
},
});
@@ -278,6 +278,12 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="px-4">
<AlertBlock type="info">
Changing settings such as placements may cause the logs/monitoring
to be unavailable.
</AlertBlock>
</div>
<Form {...form}>
<form
@@ -17,7 +17,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Input, NumberInput } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -125,28 +125,14 @@ export const UpdatePort = ({ portId }: Props) => {
<FormItem>
<FormLabel>Published Port</FormLabel>
<FormControl>
<Input
placeholder="1-65535"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(0);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
field.onChange(number);
}
}
}}
/>
<NumberInput placeholder="1-65535" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="targetPort"
@@ -154,22 +140,7 @@ export const UpdatePort = ({ portId }: Props) => {
<FormItem>
<FormLabel>Target Port</FormLabel>
<FormControl>
<Input
placeholder="1-65535"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(0);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
field.onChange(number);
}
}
}}
/>
<Input placeholder="1-65535" {...field} />
</FormControl>
<FormMessage />
@@ -19,6 +19,15 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -36,6 +45,36 @@ const AddRedirectchema = z.object({
type AddRedirect = z.infer<typeof AddRedirectchema>;
// Default presets
const redirectPresets = [
// {
// label: "Allow www & non-www.",
// redirect: {
// regex: "",
// permanent: false,
// replacement: "",
// },
// },
{
id: "to-www",
label: "Redirect to www",
redirect: {
regex: "^https?://(?:www.)?(.+)",
permanent: true,
replacement: "https://www.$${1}",
},
},
{
id: "to-non-www",
label: "Redirect to non-www",
redirect: {
regex: "^https?://www.(.+)",
permanent: true,
replacement: "https://$${1}",
},
},
];
interface Props {
applicationId: string;
children?: React.ReactNode;
@@ -43,9 +82,10 @@ interface Props {
export const AddRedirect = ({
applicationId,
children = <PlusIcon className="h-4 w-4" />,
children = <PlusIcon className="w-4 h-4" />,
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [presetSelected, setPresetSelected] = useState("");
const utils = api.useUtils();
const { mutateAsync, isLoading, error, isError } =
@@ -81,19 +121,36 @@ export const AddRedirect = ({
await utils.application.readTraefikConfig.invalidate({
applicationId,
});
setIsOpen(false);
onDialogToggle(false);
})
.catch(() => {
toast.error("Error to create the redirect");
});
};
const onDialogToggle = (open: boolean) => {
setIsOpen(open);
// commented for the moment because not reseting the form if accidentally closed the dialog can be considered as a feature instead of a bug
// setPresetSelected("");
// form.reset();
};
const onPresetSelect = (presetId: string) => {
const redirectPreset = redirectPresets.find(
(preset) => preset.id === presetId,
)?.redirect;
if (!redirectPreset) return;
const { regex, permanent, replacement } = redirectPreset;
form.reset({ regex, permanent, replacement }, { keepDefaultValues: true });
setPresetSelected(presetId);
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Dialog open={isOpen} onOpenChange={onDialogToggle}>
<DialogTrigger asChild>
<Button>{children}</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Redirects</DialogTitle>
<DialogDescription>
@@ -102,6 +159,24 @@ export const AddRedirect = ({
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="md:col-span-2">
<Label>Presets</Label>
<Select onValueChange={onPresetSelect} value={presetSelected}>
<SelectTrigger>
<SelectValue placeholder="No preset selected" />
</SelectTrigger>
<SelectContent>
{redirectPresets.map((preset) => (
<SelectItem key={preset.label} value={preset.id}>
{preset.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Separator />
<Form {...form}>
<form
id="hook-form-add-redirect"
@@ -142,7 +217,7 @@ export const AddRedirect = ({
control={form.control}
name="permanent"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
<div className="space-y-0.5">
<FormLabel>Permanent</FormLabel>
<FormDescription>
@@ -80,7 +80,7 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
<CardHeader>
<CardTitle className="text-xl">Resources</CardTitle>
<CardDescription>
If you want to decrease or increase the resources to a specific
If you want to decrease or increase the resources to a specific.
application or database
</CardDescription>
</CardHeader>
@@ -7,7 +7,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { File } from "lucide-react";
import { File, Loader2 } from "lucide-react";
import React from "react";
import { UpdateTraefikConfig } from "./update-traefik-config";
interface Props {
@@ -15,7 +15,7 @@ interface Props {
}
export const ShowTraefikConfig = ({ applicationId }: Props) => {
const { data } = api.application.readTraefikConfig.useQuery(
const { data, isLoading } = api.application.readTraefikConfig.useQuery(
{
applicationId,
},
@@ -35,7 +35,12 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4">
{data === null ? (
{isLoading ? (
<span className="text-base text-muted-foreground flex flex-row gap-3 items-center justify-center min-h-[10vh]">
Loading...
<Loader2 className="animate-spin" />
</span>
) : !data ? (
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
<File className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
@@ -7,7 +7,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { AlertTriangle, Package } from "lucide-react";
import { Package } from "lucide-react";
import React from "react";
import { AddVolumes } from "./add-volumes";
import { DeleteVolume } from "./delete-volume";
@@ -11,24 +11,42 @@ interface Props {
logPath: string | null;
open: boolean;
onClose: () => void;
serverId?: string;
}
export const ShowDeployment = ({ logPath, open, onClose }: Props) => {
export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
const [data, setData] = useState("");
const endOfLogsRef = useRef<HTMLDivElement>(null);
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
useEffect(() => {
if (!open || !logPath) return;
setData("");
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}`;
const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}${serverId ? `&serverId=${serverId}` : ""}`;
const ws = new WebSocket(wsUrl);
wsRef.current = ws; // Store WebSocket instance in ref
ws.onmessage = (e) => {
setData((currentData) => currentData + e.data);
};
return () => ws.close();
ws.onerror = (error) => {
console.error("WebSocket error: ", error);
};
ws.onclose = () => {
console.log("WebSocket connection closed");
wsRef.current = null; // Clear reference on close
};
return () => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
ws.close();
wsRef.current = null;
}
};
}, [logPath, open]);
const scrollToBottom = () => {
@@ -44,7 +62,15 @@ export const ShowDeployment = ({ logPath, open, onClose }: Props) => {
open={open}
onOpenChange={(e) => {
onClose();
if (!e) setData("");
if (!e) {
setData("");
}
if (wsRef.current) {
if (wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.close();
}
}
}}
>
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
@@ -25,7 +25,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
{ applicationId },
{
enabled: !!applicationId,
refetchInterval: 5000,
refetchInterval: 1000,
},
);
const [url, setUrl] = React.useState("");
@@ -110,6 +110,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
</div>
)}
<ShowDeployment
serverId={data?.serverId || ""}
open={activeLog !== null}
onClose={() => setActiveLog(null)}
logPath={activeLog}
@@ -18,7 +18,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Input, NumberInput } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -140,7 +140,7 @@ export const AddDomain = ({
<DialogTrigger className="" asChild>
{children}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Domain</DialogTitle>
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
@@ -175,6 +175,7 @@ export const AddDomain = ({
onClick={() => {
generateDomain({
appName: application?.appName || "",
serverId: application?.serverId || "",
})
.then((domain) => {
field.onChange(domain);
@@ -227,19 +228,36 @@ export const AddDomain = ({
<FormItem>
<FormLabel>Container Port</FormLabel>
<FormControl>
<Input
placeholder={"3000"}
{...field}
onChange={(e) => {
field.onChange(Number.parseInt(e.target.value));
}}
/>
<NumberInput placeholder={"3000"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="https"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
<div className="space-y-0.5">
<FormLabel>HTTPS</FormLabel>
<FormDescription>
Automatically provision SSL Certificate.
</FormDescription>
<FormMessage />
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
{form.getValues().https && (
<FormField
control={form.control}
@@ -269,38 +287,12 @@ export const AddDomain = ({
)}
/>
)}
<FormField
control={form.control}
name="https"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>HTTPS</FormLabel>
<FormDescription>
Automatically provision SSL Certificate.
</FormDescription>
<FormMessage />
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
</div>
</form>
<DialogFooter>
<Button
isLoading={form.formState.isSubmitting}
form="hook-form"
type="submit"
>
<Button isLoading={isLoading} form="hook-form" type="submit">
{dictionary.submit}
</Button>
</DialogFooter>
@@ -52,7 +52,7 @@ export const ShowDomains = ({ applicationId }: Props) => {
<div className="flex w-full flex-col items-center justify-center gap-3">
<GlobeIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To access to the application is required to set at least 1
To access the application it is required to set at least 1
domain
</span>
<div className="flex flex-row gap-4 flex-wrap">
@@ -45,14 +45,17 @@ export const DeployApplication = ({ applicationId }: Props) => {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
toast.success("Deploying Application....");
await refetch();
await deploy({
applicationId,
}).catch(() => {
toast.error("Error to deploy Application");
});
})
.then(async () => {
toast.success("Application deployed succesfully");
await refetch();
})
.catch(() => {
toast.error("Error to deploy Application");
});
await refetch();
}}
@@ -130,7 +130,7 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
type="submit"
className="w-fit"
isLoading={isLoading}
disabled={!zip}
disabled={!zip || isLoading}
>
Deploy{" "}
</Button>
@@ -66,7 +66,10 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
) : (
<StopApplication applicationId={applicationId} />
)}
<DockerTerminalModal appName={data?.appName || ""}>
<DockerTerminalModal
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
Open Terminal
@@ -16,6 +16,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
export const DockerLogs = dynamic(
@@ -30,12 +31,14 @@ export const DockerLogs = dynamic(
interface Props {
appName: string;
serverId?: string;
}
export const ShowDockerLogs = ({ appName }: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
export const ShowDockerLogs = ({ appName, serverId }: Props) => {
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
serverId,
},
{
enabled: !!appName,
@@ -62,7 +65,14 @@ export const ShowDockerLogs = ({ appName }: Props) => {
<Label>Select a container to view logs</Label>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -79,6 +89,7 @@ export const ShowDockerLogs = ({ appName }: Props) => {
</SelectContent>
</Select>
<DockerLogs
serverId={serverId || ""}
id="terminal"
containerId={containerId || "select-a-container"}
/>
@@ -9,26 +9,50 @@ import { useEffect, useRef, useState } from "react";
interface Props {
logPath: string | null;
serverId?: string;
open: boolean;
onClose: () => void;
}
export const ShowDeploymentCompose = ({ logPath, open, onClose }: Props) => {
export const ShowDeploymentCompose = ({
logPath,
open,
onClose,
serverId,
}: Props) => {
const [data, setData] = useState("");
const endOfLogsRef = useRef<HTMLDivElement>(null);
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
useEffect(() => {
if (!open || !logPath) return;
setData("");
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}`;
const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}&serverId=${serverId}`;
const ws = new WebSocket(wsUrl);
wsRef.current = ws; // Store WebSocket instance in ref
ws.onmessage = (e) => {
setData((currentData) => currentData + e.data);
};
return () => ws.close();
ws.onerror = (error) => {
console.error("WebSocket error: ", error);
};
ws.onclose = () => {
console.log("WebSocket connection closed");
wsRef.current = null;
};
return () => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
ws.close();
wsRef.current = null;
}
};
}, [logPath, open]);
const scrollToBottom = () => {
@@ -44,7 +68,15 @@ export const ShowDeploymentCompose = ({ logPath, open, onClose }: Props) => {
open={open}
onOpenChange={(e) => {
onClose();
if (!e) setData("");
if (!e) {
setData("");
}
if (wsRef.current) {
if (wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.close();
}
}
}}
>
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
@@ -111,6 +111,7 @@ export const ShowDeploymentsCompose = ({ composeId }: Props) => {
</div>
)}
<ShowDeploymentCompose
serverId={data?.serverId || ""}
open={activeLog !== null}
onClose={() => setActiveLog(null)}
logPath={activeLog}
@@ -18,7 +18,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Input, NumberInput } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -161,7 +161,7 @@ export const AddDomainCompose = ({
<DialogTrigger className="" asChild>
{children}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Domain</DialogTitle>
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
@@ -190,7 +190,7 @@ export const AddDomainCompose = ({
{errorServices?.message}
</AlertBlock>
)}
<div className="flex flex-row gap-4 w-full items-end">
<div className="flex flex-row items-end w-full gap-4">
<FormField
control={form.control}
name="serviceName"
@@ -310,6 +310,7 @@ export const AddDomainCompose = ({
isLoading={isLoadingGenerate}
onClick={() => {
generateDomain({
serverId: compose?.serverId || "",
appName: compose?.appName || "",
})
.then((domain) => {
@@ -363,19 +364,36 @@ export const AddDomainCompose = ({
<FormItem>
<FormLabel>Container Port</FormLabel>
<FormControl>
<Input
placeholder={"3000"}
{...field}
onChange={(e) => {
field.onChange(Number.parseInt(e.target.value));
}}
/>
<NumberInput placeholder={"3000"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="https"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
<div className="space-y-0.5">
<FormLabel>HTTPS</FormLabel>
<FormDescription>
Automatically provision SSL Certificate.
</FormDescription>
<FormMessage />
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
{https && (
<FormField
control={form.control}
@@ -405,28 +423,6 @@ export const AddDomainCompose = ({
)}
/>
)}
<FormField
control={form.control}
name="https"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>HTTPS</FormLabel>
<FormDescription>
Automatically provision SSL Certificate.
</FormDescription>
<FormMessage />
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
</div>
</form>
@@ -53,7 +53,7 @@ export const ShowDomainsCompose = ({ composeId }: Props) => {
<div className="flex w-full flex-col items-center justify-center gap-3">
<GlobeIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To access to the application is required to set at least 1
To access to the application it is required to set at least 1
domain
</span>
<div className="flex flex-row gap-4 flex-wrap">
@@ -75,7 +75,10 @@ export const ComposeActions = ({ composeId }: Props) => {
<StopCompose composeId={composeId} />
)}
<DockerTerminalModal appName={data?.appName || ""}>
<DockerTerminalModal
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
Open Terminal
@@ -77,7 +77,6 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
});
})
.catch((e) => {
console.log(e);
toast.error("Error to update the compose config");
});
};
@@ -11,7 +11,7 @@ import {
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import { Puzzle, RefreshCw } from "lucide-react";
import { useState } from "react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
interface Props {
@@ -34,6 +34,16 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
const { mutateAsync, isLoading } = api.compose.fetchSourceType.useMutation();
useEffect(() => {
if (isOpen) {
mutateAsync({ composeId })
.then(() => {
refetch();
})
.catch((err) => {});
}
}, [isOpen]);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
@@ -16,6 +16,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { Loader, Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
export const DockerLogs = dynamic(
@@ -30,14 +31,20 @@ export const DockerLogs = dynamic(
interface Props {
appName: string;
serverId?: string;
appType: "stack" | "docker-compose";
}
export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
export const ShowDockerLogsCompose = ({
appName,
appType,
serverId,
}: Props) => {
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
appType,
serverId,
},
{
enabled: !!appName,
@@ -64,7 +71,14 @@ export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
<Label>Select a container to view logs</Label>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -81,6 +95,7 @@ export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
</SelectContent>
</Select>
<DockerLogs
serverId={serverId || ""}
id="terminal"
containerId={containerId || "select-a-container"}
/>
@@ -17,23 +17,27 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { DockerMonitoring } from "../../monitoring/docker/show";
interface Props {
appName: string;
serverId?: string;
appType: "stack" | "docker-compose";
}
export const ShowMonitoringCompose = ({
appName,
appType = "stack",
serverId,
}: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName: appName,
appType,
serverId,
},
{
enabled: !!appName,
@@ -46,7 +50,7 @@ export const ShowMonitoringCompose = ({
const [containerId, setContainerId] = useState<string | undefined>();
const { mutateAsync: restart, isLoading } =
const { mutateAsync: restart, isLoading: isRestarting } =
api.docker.restartContainer.useMutation();
useEffect(() => {
@@ -77,7 +81,14 @@ export const ShowMonitoringCompose = ({
value={containerAppName}
>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -95,7 +106,7 @@ export const ShowMonitoringCompose = ({
</SelectContent>
</Select>
<Button
isLoading={isLoading}
isLoading={isRestarting}
onClick={async () => {
if (!containerId) return;
toast.success(`Restarting container ${containerAppName}`);
@@ -11,12 +11,14 @@ import { api } from "@/utils/api";
interface Props {
containerId: string;
serverId?: string;
}
export const ShowContainerConfig = ({ containerId }: Props) => {
export const ShowContainerConfig = ({ containerId, serverId }: Props) => {
const { data } = api.docker.getConfig.useQuery(
{
containerId,
serverId,
},
{
enabled: !!containerId,
@@ -1,24 +1,41 @@
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Terminal } from "@xterm/xterm";
import React, { useEffect } from "react";
import React, { useEffect, useRef } from "react";
import { FitAddon } from "xterm-addon-fit";
import "@xterm/xterm/css/xterm.css";
interface Props {
id: string;
containerId: string;
serverId?: string | null;
}
export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
export const DockerLogsId: React.FC<Props> = ({
id,
containerId,
serverId,
}) => {
const [term, setTerm] = React.useState<Terminal>();
const [lines, setLines] = React.useState<number>(40);
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
const createTerminal = (): Terminal => {
useEffect(() => {
// if (containerId === "select-a-container") {
// return;
// }
const container = document.getElementById(id);
if (container) {
container.innerHTML = "";
}
if (wsRef.current) {
console.log(wsRef.current);
if (wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.close();
}
wsRef.current = null;
}
const termi = new Terminal({
cursorBlink: true,
cols: 80,
@@ -38,9 +55,9 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/docker-container-logs?containerId=${containerId}&tail=${lines}`;
const wsUrl = `${protocol}//${window.location.host}/docker-container-logs?containerId=${containerId}&tail=${lines}${serverId ? `&serverId=${serverId}` : ""}`;
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
const fitAddon = new FitAddon();
termi.loadAddon(fitAddon);
// @ts-ignore
@@ -49,6 +66,10 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
termi.focus();
setTerm(termi);
ws.onerror = (error) => {
console.error("WebSocket error: ", error);
};
ws.onmessage = (e) => {
termi.write(e.data);
};
@@ -57,12 +78,14 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
console.log(e.reason);
termi.write(`Connection closed!\nReason: ${e.reason}\n`);
wsRef.current = null;
};
return () => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
ws.close();
wsRef.current = null;
}
};
return termi;
};
useEffect(() => {
createTerminal();
}, [lines, containerId]);
useEffect(() => {
@@ -22,9 +22,14 @@ export const DockerLogsId = dynamic(
interface Props {
containerId: string;
children?: React.ReactNode;
serverId?: string | null;
}
export const ShowDockerModalLogs = ({ containerId, children }: Props) => {
export const ShowDockerModalLogs = ({
containerId,
children,
serverId,
}: Props) => {
return (
<Dialog>
<DialogTrigger asChild>
@@ -41,7 +46,11 @@ export const ShowDockerModalLogs = ({ containerId, children }: Props) => {
<DialogDescription>View the logs for {containerId}</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4 pt-2.5">
<DockerLogsId id="terminal" containerId={containerId || ""} />
<DockerLogsId
id="terminal"
containerId={containerId || ""}
serverId={serverId}
/>
</div>
</DialogContent>
</Dialog>
@@ -114,11 +114,20 @@ export const columns: ColumnDef<Container>[] = [
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<ShowDockerModalLogs containerId={container.containerId}>
<ShowDockerModalLogs
containerId={container.containerId}
serverId={container.serverId}
>
View Logs
</ShowDockerModalLogs>
<ShowContainerConfig containerId={container.containerId} />
<DockerTerminalModal containerId={container.containerId}>
<ShowContainerConfig
containerId={container.containerId}
serverId={container.serverId || ""}
/>
<DockerTerminalModal
containerId={container.containerId}
serverId={container.serverId || ""}
>
Terminal
</DockerTerminalModal>
</DropdownMenuContent>
@@ -34,8 +34,15 @@ export type Container = NonNullable<
RouterOutputs["docker"]["getContainers"]
>[0];
export const ShowContainers = () => {
const { data, isLoading } = api.docker.getContainers.useQuery();
interface Props {
serverId?: string;
}
export const ShowContainers = ({ serverId }: Props) => {
const { data, isLoading } = api.docker.getContainers.useQuery({
serverId,
});
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
@@ -103,83 +110,99 @@ export const ShowContainers = () => {
</DropdownMenu>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
{isLoading ? (
<div className="w-full flex-col gap-2 flex items-center justify-center h-[55vh]">
<span className="text-muted-foreground text-lg font-medium">
Loading...
</span>
</div>
) : data?.length === 0 ? (
<div className="flex-col gap-2 flex items-center justify-center h-[55vh]">
<span className="text-muted-foreground text-lg font-medium">
No results.
</span>
</div>
) : (
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
{isLoading ? (
<div className="w-full flex-col gap-2 flex items-center justify-center h-[55vh]">
<span className="text-muted-foreground text-lg font-medium">
Loading...
</span>
</div>
) : (
<>No results.</>
)}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
))}
</TableHeader>
<TableBody>
{table?.getRowModel()?.rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
{isLoading ? (
<div className="w-full flex-col gap-2 flex items-center justify-center h-[55vh]">
<span className="text-muted-foreground text-lg font-medium">
Loading...
</span>
</div>
) : (
<>No results.</>
)}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)}
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="space-x-2 flex flex-wrap">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
{data && data?.length > 0 && (
<div className="flex items-center justify-end space-x-2 py-4">
<div className="space-x-2 flex flex-wrap">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
)}
</div>
</div>
);

Some files were not shown because too many files have changed in this diff Show More