Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/api/.env
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,5 @@ ADMIN_SNAPSHOT_QUOTA=100
ADMIN_MAX_SNAPSHOT_SIZE=100
ADMIN_VOLUME_QUOTA=0

SKIP_USER_EMAIL_VERIFICATION=true
SKIP_USER_EMAIL_VERIFICATION=true
DONT_SERVE_DASHBOARD=true
2 changes: 2 additions & 0 deletions apps/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ ENV SKIP_USER_EMAIL_VERIFICATION=true

ENV RUN_MIGRATIONS=true

ENV DONT_SERVE_DASHBOARD=false

HEALTHCHECK CMD [ "curl", "-f", "http://localhost:3000/api/config" ]

ENTRYPOINT ["sh", "-c", "dockerd-entrypoint.sh & node dist/apps/api/main.js"]
22 changes: 16 additions & 6 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,22 @@ import { getPinoTransport, swapMessageAndObject } from './common/utils/pino.util
cacheControl: false,
},
}),
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'dashboard'),
exclude: ['/api/*'],
renderPath: '/',
serveStaticOptions: {
cacheControl: false,
ServeStaticModule.forRootAsync({
inject: [TypedConfigService],
useFactory: (configService: TypedConfigService) => {
if (configService.get('dontServeDashboard')) {
return []
}
return [
{
rootPath: join(__dirname, '..', 'dashboard'),
exclude: ['/api/*'],
renderPath: '/',
serveStaticOptions: {
cacheControl: false,
},
},
]
},
}),
ThrottlerModule.forRoot([
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ const configuration = {
volumeQuota: parseInt(process.env.ADMIN_VOLUME_QUOTA || '0', 10),
},
skipUserEmailVerification: process.env.SKIP_USER_EMAIL_VERIFICATION === 'true',
dontServeDashboard: process.env.DONT_SERVE_DASHBOARD === 'true',
}

export { configuration }
2 changes: 1 addition & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const httpsOptions: HttpsOptions = {

async function bootstrap() {
if (process.env.OTEL_ENABLED === 'true') {
await otelSdk.start()
otelSdk.start()
}
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
bufferLogs: true,
Expand Down
25 changes: 25 additions & 0 deletions apps/dashboard/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM node:20-alpine AS builder

RUN npm install -g corepack && corepack enable

WORKDIR /daytona

COPY . .

RUN yarn

RUN VITE_BASE_API_URL=%DAYTONA_BASE_API_URL% yarn nx build dashboard --configuration=production --nxBail=true

FROM nginx:alpine as dashboard

COPY --from=builder /daytona/dist/apps/dashboard /usr/share/nginx/html
COPY --from=builder /daytona/apps/dashboard/docker/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /daytona/apps/dashboard/docker/entrypoint.sh /entrypoint.sh

ENV DAYTONA_BASE_API_URL="http://api:3000"

EXPOSE 80

HEALTHCHECK CMD [ "wget", "-q", "--spider", "http://localhost/" ]

ENTRYPOINT ["/entrypoint.sh"]
23 changes: 23 additions & 0 deletions apps/dashboard/docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: AGPL-3.0

set -e

# Validate DAYTONA_BASE_API_URL is a well-formed URL
if ! echo "$DAYTONA_BASE_API_URL" | grep -Eq '^https?://[a-zA-Z0-9./?=_-]*$'; then
echo "Error: DAYTONA_BASE_API_URL is not a valid URL."
exit 1
fi

# Escape characters that could break sed replacement
escape_sed() {
printf '%s' "$1" | sed -e 's/[\/&|\\]/\\&/g'
}
DAYTONA_BASE_API_URL_ESCAPED=$(escape_sed "$DAYTONA_BASE_API_URL")

# Replace %DAYTONA_BASE_API_URL% with actual environment variable value
find /usr/share/nginx/html -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i "s|%DAYTONA_BASE_API_URL%|${DAYTONA_BASE_API_URL_ESCAPED}|g" {} +

# Start nginx
exec nginx -g "daemon off;"
15 changes: 15 additions & 0 deletions apps/dashboard/docker/nginx/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
absolute_redirect off;

location = / {
return 301 /dashboard;
}

location /dashboard {
try_files $uri $uri/ /index.html;
}
}
9 changes: 8 additions & 1 deletion apps/dashboard/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
"options": {
"command": "cd {projectRoot} && prettier --write \"**/*.{ts,tsx,js,jsx,json,css,mjs,html}\" --config ../../.prettierrc"
}
}
},
"check-version-env": {},
"docker": {
"options": {
"target": "dashboard"
}
},
"push-manifest": {}
}
}
1 change: 1 addition & 0 deletions apps/docs/src/content/docs/en/oss-deployment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Below is a full list of environment variables with their default values:
| `ADMIN_MAX_SNAPSHOT_SIZE` | number | `100` | Admin max snapshot size, used only upon initial setup |
| `ADMIN_VOLUME_QUOTA` | number | `0` | Admin volume quota, used only upon initial setup |
| `SKIP_USER_EMAIL_VERIFICATION` | boolean | `true` | Skip user email verification process |
| `DONT_SERVE_DASHBOARD` | boolean | `false` | Disable serving dashboard static files from API service |

### Runner

Expand Down
Loading