diff --git a/.github/actions/docker-build-and-test/action.yml b/.github/actions/docker-build-and-test/action.yml
new file mode 100644
index 00000000000000..e0d68287f38487
--- /dev/null
+++ b/.github/actions/docker-build-and-test/action.yml
@@ -0,0 +1,182 @@
+name: "Docker Build and Test"
+description: "Reusable action to build and test Docker images for Cal.com"
+
+inputs:
+ platform:
+ description: "Target platform (linux/amd64 or arm64)"
+ required: true
+ platform-suffix:
+ description: "Suffix to add to image tags (e.g., -arm)"
+ required: false
+ default: ""
+ dockerhub-username:
+ description: "Docker Hub username"
+ required: true
+ dockerhub-token:
+ description: "Docker Hub token"
+ required: true
+ github-token:
+ description: "GitHub token for GHCR"
+ required: true
+ postgres-user:
+ description: "PostgreSQL user"
+ required: true
+ postgres-password:
+ description: "PostgreSQL password"
+ required: true
+ postgres-db:
+ description: "PostgreSQL database name"
+ required: true
+ database-host:
+ description: "Database host"
+ required: true
+ push-image:
+ description: "Whether to push the built image"
+ required: false
+ default: "false"
+
+runs:
+ using: "composite"
+ steps:
+ - name: Log in to the Docker Hub registry
+ uses: docker/login-action@v3
+ with:
+ username: ${{ inputs.dockerhub-username }}
+ password: ${{ inputs.dockerhub-token }}
+ logout: true
+
+ - name: Log in to the Github Container registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ inputs.github-token }}
+
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: |
+ docker.io/calendso/calendso
+ docker.io/calcom/cal.com
+ ghcr.io/calcom/cal.com
+ flavor: |
+ latest=${{ !github.event.release.prerelease }}
+ suffix=${{ inputs.platform-suffix }}
+
+ - name: Copy env
+ shell: bash
+ run: |
+ grep -o '^[^#]*' .env.example > .env
+ cat .env >> $GITHUB_ENV
+ echo "DATABASE_HOST=localhost:5432" >> $GITHUB_ENV
+ eval $(sed -e '/^#/d' -e 's/^/export /' -e 's/$/;/' .env) ;
+
+ - name: Start database
+ shell: bash
+ run: |
+ docker compose up -d database
+
+ - name: Show database logs and container status
+ shell: bash
+ run: |
+ echo "--- Container Status ---"
+ docker compose ps database
+
+ echo "--- Container Logs ---"
+ docker compose logs database
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ with:
+ driver-opts: |
+ network=container:database
+ buildkitd-flags: |
+ --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host
+
+ - name: Build image
+ id: docker_build
+ uses: docker/build-push-action@v6
+ with:
+ context: ./
+ file: ./Dockerfile
+ load: true
+ push: false
+ platforms: ${{ inputs.platform }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ build-args: |
+ NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
+ NEXT_PUBLIC_API_V2_URL=http://localhost:5555/api/v2
+ NEXT_PUBLIC_LICENSE_CONSENT=agree
+ DATABASE_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
+ DATABASE_DIRECT_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
+
+ - name: Test runtime
+ shell: bash
+ run: |
+ tags="${{ steps.meta.outputs.tags }}"
+ IFS=',' read -ra ADDR <<< "$tags"
+ tag=${ADDR[0]}
+
+ docker run --rm --network stack \
+ -p 3000:3000 \
+ -e DATABASE_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@database/${{ inputs.postgres-db }} \
+ -e DATABASE_DIRECT_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@database/${{ inputs.postgres-db }} \
+ -e NEXTAUTH_SECRET=${{ env.NEXTAUTH_SECRET }} \
+ -e CALENDSO_ENCRYPTION_KEY=${{ env.CALENDSO_ENCRYPTION_KEY }} \
+ $tag &
+
+ server_pid=$!
+
+ echo "Waiting for the server to start..."
+ sleep 120
+
+ echo http://localhost:3000/auth/login
+
+ for i in {1..60}; do
+ echo "Checking server health ($i/60)..."
+ response=$(curl -o /dev/null -s -w "%{http_code}" http://localhost:3000/auth/login)
+ echo "HTTP Status Code: $response"
+ if [[ "$response" == "200" ]] || [[ "$response" == "307" ]]; then
+ echo "Server is healthy"
+ kill $server_pid
+ exit 0
+ fi
+ sleep 1
+ done
+
+ echo "Server health check failed"
+ kill $server_pid
+ exit 1
+ env:
+ NEXTAUTH_SECRET: "EI4qqDpcfdvf4A+0aQEEx8JjHxHSy4uWiZw/F32K+pA="
+ CALENDSO_ENCRYPTION_KEY: "0zfLtY99wjeLnsM7qsa8xsT+Q0oSgnOL"
+
+ - name: Push image
+ id: docker_push
+ uses: docker/build-push-action@v6
+ if: ${{ inputs.push-image == 'true' && !github.event.release.prerelease }}
+ with:
+ context: ./
+ file: ./Dockerfile
+ push: true
+ platforms: ${{ inputs.platform }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ build-args: |
+ NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
+ NEXT_PUBLIC_API_V2_URL=http://localhost:5555/api/v2
+ NEXT_PUBLIC_LICENSE_CONSENT=agree
+ DATABASE_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
+ DATABASE_DIRECT_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
+
+ - name: Image digest
+ shell: bash
+ run: echo ${{ steps.docker_build.outputs.digest }}
+
+ - name: Cleanup
+ shell: bash
+ if: always()
+ run: |
+ docker compose down
diff --git a/.github/workflows/release-docker.yaml b/.github/workflows/release-docker.yaml
index 34098b595b4993..b9b9bb5b9964ad 100644
--- a/.github/workflows/release-docker.yaml
+++ b/.github/workflows/release-docker.yaml
@@ -1,10 +1,15 @@
name: "Release Docker"
-on: # yamllint disable-line rule:truthy
- release:
- types:
- - created
- - published
+env:
+ POSTGRES_USER: "unicorn_user"
+ POSTGRES_PASSWORD: "magical_password"
+ POSTGRES_DB: "calendso"
+ DATABASE_HOST: "database:5432"
+
+on:
+ push:
+ tags:
+ - "v*"
# in case manual trigger is needed
workflow_dispatch:
inputs:
@@ -12,11 +17,55 @@ on: # yamllint disable-line rule:truthy
description: "v{Major}.{Minor}.{Patch}"
jobs:
- release:
- name: "Remote Release"
+ release-amd64:
+ name: "Release AMD64"
+ runs-on: buildjet-4vcpu-ubuntu-2204
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: "Determine tag"
+ run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV'
- runs-on: "ubuntu-latest"
+ - name: Build and test Docker image
+ uses: ./.github/actions/docker-build-and-test
+ with:
+ platform: "linux/amd64"
+ platform-suffix: ""
+ dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
+ dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ postgres-user: ${{ env.POSTGRES_USER }}
+ postgres-password: ${{ env.POSTGRES_PASSWORD }}
+ postgres-db: ${{ env.POSTGRES_DB }}
+ database-host: ${{ env.DATABASE_HOST }}
+ push-image: "true"
+ - name: Notify Slack on Success
+ if: success()
+ uses: slackapi/slack-github-action@v1.24.0
+ with:
+ payload: |
+ {
+ "text": ":large_green_circle: Workflow *${{ github.workflow }}* (AMD64) succeeded in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
+
+ - name: Notify Slack on Failure
+ if: failure()
+ uses: slackapi/slack-github-action@v1.24.0
+ with:
+ payload: |
+ {
+ "text": ":red_circle: Workflow *${{ github.workflow }}* (AMD64) failed in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
+
+ release-arm:
+ name: "Release ARM"
+ runs-on: ubuntu-24.04-arm
steps:
- name: checkout
uses: actions/checkout@v4
@@ -24,25 +73,38 @@ jobs:
- name: "Determine tag"
run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV'
- - name: "Run remote release workflow"
- uses: "actions/github-script@v6"
+ - name: Build and test Docker image
+ uses: ./.github/actions/docker-build-and-test
+ with:
+ platform: "arm64"
+ platform-suffix: "-arm"
+ dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
+ dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ postgres-user: ${{ env.POSTGRES_USER }}
+ postgres-password: ${{ env.POSTGRES_PASSWORD }}
+ postgres-db: ${{ env.POSTGRES_DB }}
+ database-host: ${{ env.DATABASE_HOST }}
+ push-image: "true"
+
+ - name: Notify Slack on Success
+ if: success()
+ uses: slackapi/slack-github-action@v1.24.0
+ with:
+ payload: |
+ {
+ "text": ":large_green_circle: Workflow *${{ github.workflow }}* (ARM) succeeded in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
+
+ - name: Notify Slack on Failure
+ if: failure()
+ uses: slackapi/slack-github-action@v1.24.0
with:
- # Requires a personal access token with Actions Read and write permissions on calcom/docker.
- github-token: "${{ secrets.DOCKER_REPO_ACCESS_TOKEN }}"
- script: |
- try {
- const response = await github.rest.actions.createWorkflowDispatch({
- owner: context.repo.owner,
- repo: 'docker',
- workflow_id: 'create-release.yaml',
- ref: 'main',
- inputs: {
- "RELEASE_TAG": process.env.RELEASE_TAG
- },
- });
-
- console.log(response);
- } catch (error) {
- console.error(error);
- core.setFailed(error.message);
+ payload: |
+ {
+ "text": ":red_circle: Workflow *${{ github.workflow }}* (ARM) failed in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000000000..0f978473368191
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,91 @@
+FROM --platform=$BUILDPLATFORM node:20 AS builder
+
+WORKDIR /calcom
+
+## If we want to read any ENV variable from .env file, we need to first accept and pass it as an argument to the Dockerfile
+ARG NEXT_PUBLIC_LICENSE_CONSENT
+ARG NEXT_PUBLIC_WEBSITE_TERMS_URL
+ARG NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL
+ARG CALCOM_TELEMETRY_DISABLED
+ARG DATABASE_URL
+ARG NEXTAUTH_SECRET=secret
+ARG CALENDSO_ENCRYPTION_KEY=secret
+ARG MAX_OLD_SPACE_SIZE=4096
+ARG NEXT_PUBLIC_API_V2_URL
+ARG CSP_POLICY
+
+## We need these variables as required by Next.js build to create rewrites
+ARG NEXT_PUBLIC_SINGLE_ORG_SLUG
+ARG ORGANIZATIONS_ENABLED
+
+ENV NEXT_PUBLIC_WEBAPP_URL=http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER \
+ NEXT_PUBLIC_API_V2_URL=$NEXT_PUBLIC_API_V2_URL \
+ NEXT_PUBLIC_LICENSE_CONSENT=$NEXT_PUBLIC_LICENSE_CONSENT \
+ NEXT_PUBLIC_WEBSITE_TERMS_URL=$NEXT_PUBLIC_WEBSITE_TERMS_URL \
+ NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL=$NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL \
+ CALCOM_TELEMETRY_DISABLED=$CALCOM_TELEMETRY_DISABLED \
+ DATABASE_URL=$DATABASE_URL \
+ DATABASE_DIRECT_URL=$DATABASE_URL \
+ NEXTAUTH_SECRET=${NEXTAUTH_SECRET} \
+ CALENDSO_ENCRYPTION_KEY=${CALENDSO_ENCRYPTION_KEY} \
+ NEXT_PUBLIC_SINGLE_ORG_SLUG=$NEXT_PUBLIC_SINGLE_ORG_SLUG \
+ ORGANIZATIONS_ENABLED=$ORGANIZATIONS_ENABLED \
+ NODE_OPTIONS=--max-old-space-size=${MAX_OLD_SPACE_SIZE} \
+ BUILD_STANDALONE=true \
+ CSP_POLICY=$CSP_POLICY
+
+COPY package.json yarn.lock .yarnrc.yml playwright.config.ts turbo.json i18n.json ./
+COPY .yarn ./.yarn
+COPY apps/web ./apps/web
+COPY apps/api/v2 ./apps/api/v2
+COPY packages ./packages
+COPY tests ./tests
+
+RUN yarn config set httpTimeout 1200000
+RUN npx turbo prune --scope=@calcom/web --scope=@calcom/trpc --docker
+RUN yarn install
+# Build and make embed servable from web/public/embed folder
+RUN yarn workspace @calcom/trpc run build
+RUN yarn --cwd packages/embeds/embed-core workspace @calcom/embed-core run build
+RUN yarn --cwd apps/web workspace @calcom/web run build
+RUN rm -rf node_modules/.cache .yarn/cache apps/web/.next/cache
+
+FROM node:20 AS builder-two
+
+WORKDIR /calcom
+ARG NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
+
+ENV NODE_ENV=production
+
+COPY package.json .yarnrc.yml turbo.json i18n.json ./
+COPY .yarn ./.yarn
+COPY --from=builder /calcom/yarn.lock ./yarn.lock
+COPY --from=builder /calcom/node_modules ./node_modules
+COPY --from=builder /calcom/packages ./packages
+COPY --from=builder /calcom/apps/web ./apps/web
+COPY --from=builder /calcom/packages/prisma/schema.prisma ./prisma/schema.prisma
+COPY scripts scripts
+RUN chmod +x scripts/*
+
+# Save value used during this build stage. If NEXT_PUBLIC_WEBAPP_URL and BUILT_NEXT_PUBLIC_WEBAPP_URL differ at
+# run-time, then start.sh will find/replace static values again.
+ENV NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL \
+ BUILT_NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL
+
+RUN scripts/replace-placeholder.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_WEBAPP_URL}
+
+FROM node:20 AS runner
+
+WORKDIR /calcom
+COPY --from=builder-two /calcom ./
+ARG NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
+ENV NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL \
+ BUILT_NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL
+
+ENV NODE_ENV=production
+EXPOSE 3000
+
+HEALTHCHECK --interval=30s --timeout=30s --retries=5 \
+ CMD wget --spider http://localhost:3000 || exit 1
+
+CMD ["/calcom/scripts/start.sh"]
diff --git a/README.md b/README.md
index bae70679f9e076..fca8f7b6bba188 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-
The open-source Calendly successor.
@@ -175,6 +175,7 @@ yarn dx
```
#### Development tip
+
1. Add `export NODE_OPTIONS=“--max-old-space-size=16384”` to your shell script to increase the memory limit for the node process. Alternatively, you can run this in your terminal before running the app. Replace 16384 with the amount of RAM you want to allocate to the node process.
2. Add `NEXT_PUBLIC_LOGGER_LEVEL={level}` to your .env file to control the logging verbosity for all tRPC queries and mutations.\
@@ -226,7 +227,7 @@ for Logger level to be set at info, for example.
3. Now open your psql shell with the DB you created: `psql -h localhost -U postgres -d
### Railway
@@ -425,7 +679,7 @@ See the [roadmap project](https://cal.com/roadmap) for a list of proposed featur
Cal.com, Inc. is a commercial open source company, which means some parts of this open source repository require a commercial license. The concept is called "Open Core" where the core technology (99%) is fully open source, licensed under [AGPLv3](https://opensource.org/license/agpl-v3) and the last 1% is covered under a commercial license (["/ee" Enterprise Edition](https://github.com/calcom/cal.com/tree/main/packages/features/ee)) which we believe is entirely relevant for larger organisations that require enterprise features. Enterprise features are built by the core engineering team of Cal.com, Inc. which is hired in full-time. Find their compensation on https://cal.com/open.
-> [!NOTE]
+> [!NOTE]
> Our philosophy is simple, all "Singleplayer APIs" are open-source under AGPLv3. All commercial "Multiplayer APIs" are under a commercial license.
| | AGPLv3 | EE |
@@ -497,8 +751,8 @@ Don't code but still want to contribute? Join our [Discussions](https://github.c
- Set CSP_POLICY="non-strict" env variable, which enables [Strict CSP](https://web.dev/strict-csp/) except for unsafe-inline in style-src . If you have some custom changes in your instance, you might have to make some code change to make your instance CSP compatible. Right now it enables strict CSP only on login page and on other SSR pages it is enabled in Report only mode to detect possible issues. On, SSG pages it is still not supported.
## Single Org Mode
-Refer to docs [here](./docs/self-hosting/guides/organization/single-organization-setup) for a detailed documentation with screenshots.
+Refer to docs [here](./docs/self-hosting/guides/organization/single-organization-setup) for a detailed documentation with screenshots.
## Integrations
@@ -655,6 +909,7 @@ following
We use changesets to generate changelogs and publish public packages (packages with `private: true` are ignored).
An example of good readme is [atoms readme](https://github.com/calcom/cal.com/blob/main/packages/platform/atoms/README.md). Every public package must:
+
1. Follow semantic versioning when using changesets.
2. Mark breaking changes using `❗️Breaking change`
diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore
new file mode 100644
index 00000000000000..9523dceaebecbd
--- /dev/null
+++ b/apps/web/.dockerignore
@@ -0,0 +1,3 @@
+.git
+.github
+.env.example
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000000000..3bfa8421c29cd3
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,138 @@
+# Use postgres/example user/password credentials
+
+volumes:
+ database-data:
+ redis-data:
+
+networks:
+ stack:
+ name: stack
+ external: false
+
+services:
+ database:
+ container_name: database
+ image: postgres
+ restart: always
+ volumes:
+ - database-data:/var/lib/postgresql
+ environment:
+ - POSTGRES_USER=unicorn_user
+ - POSTGRES_PASSWORD=magical_password
+ - POSTGRES_DB=calendso
+ networks:
+ - stack
+
+ redis:
+ container_name: redis
+ image: redis:latest
+ restart: always
+ volumes:
+ - redis-data:/data
+ networks:
+ - stack
+ ports:
+ - "${REDIS_PORT:-6379}:6379"
+
+ calcom:
+ image: calcom.docker.scarf.sh/calcom/cal.com
+ build:
+ context: .
+ dockerfile: Dockerfile
+ args:
+ NEXT_PUBLIC_WEBAPP_URL: ${NEXT_PUBLIC_WEBAPP_URL}
+ NEXT_PUBLIC_API_V2_URL: ${NEXT_PUBLIC_API_V2_URL}
+ NEXT_PUBLIC_LICENSE_CONSENT: ${NEXT_PUBLIC_LICENSE_CONSENT}
+ NEXT_PUBLIC_WEBSITE_TERMS_URL: ${NEXT_PUBLIC_WEBSITE_TERMS_URL}
+ NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL: ${NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL}
+ NEXT_PUBLIC_SINGLE_ORG_SLUG: ${NEXT_PUBLIC_SINGLE_ORG_SLUG}
+ ORGANIZATIONS_ENABLED: ${ORGANIZATIONS_ENABLED}
+ CALCOM_TELEMETRY_DISABLED: ${CALCOM_TELEMETRY_DISABLED}
+ NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
+ CALENDSO_ENCRYPTION_KEY: ${CALENDSO_ENCRYPTION_KEY}
+ DATABASE_URL: ${DATABASE_URL}
+ DATABASE_DIRECT_URL: ${DATABASE_URL}
+ CSP_POLICY: ${CSP_POLICY}
+ restart: always
+ networks:
+ - stack
+ ports:
+ - 3000:3000
+ env_file: .env
+ environment:
+ - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB}
+ - DATABASE_DIRECT_URL=${DATABASE_URL}
+ depends_on:
+ - database
+
+ calcom-api:
+ container_name: calcom-api
+ build:
+ context: ./calcom
+ dockerfile: apps/api/v2/Dockerfile
+ args:
+ DATABASE_URL: ${DATABASE_URL}
+ DATABASE_DIRECT_URL: ${DATABASE_URL}
+ restart: always
+ networks:
+ - stack
+ ports:
+ - "${API_PORT:-80}:${API_PORT:-80}"
+ env_file: .env
+ environment:
+ - NODE_ENV=${NODE_ENV}
+ - API_PORT=${API_PORT:-80}
+ - API_URL=${API_URL}
+ - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB}
+ - DATABASE_READ_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB}
+ - DATABASE_WRITE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB}
+ - DATABASE_DIRECT_URL=${DATABASE_URL}
+ - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
+ - JWT_SECRET=${JWT_SECRET}
+ - REDIS_URL=${REDIS_URL}
+ - LOG_LEVEL=${LOG_LEVEL}
+ - API_KEY_PREFIX=${API_KEY_PREFIX}
+ - WEB_APP_URL=${WEB_APP_URL}
+ - IS_E2E=${IS_E2E:-false}
+ - REWRITE_API_V2_PREFIX=${REWRITE_API_V2_PREFIX:-true}
+ - CALCOM_LICENSE_KEY=${CALCOM_LICENSE_KEY}
+ - NEXT_PUBLIC_VAPID_PUBLIC_KEY=${NEXT_PUBLIC_VAPID_PUBLIC_KEY}
+ - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
+ - STRIPE_API_KEY=${STRIPE_API_KEY}
+ - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
+ - STRIPE_PRICE_ID_STARTER=${STRIPE_PRICE_ID_STARTER}
+ - STRIPE_PRICE_ID_STARTER_OVERAGE=${STRIPE_PRICE_ID_STARTER_OVERAGE}
+ - STRIPE_PRICE_ID_ESSENTIALS=${STRIPE_PRICE_ID_ESSENTIALS}
+ - STRIPE_PRICE_ID_ESSENTIALS_OVERAGE=${STRIPE_PRICE_ID_ESSENTIALS_OVERAGE}
+ - STRIPE_PRICE_ID_ENTERPRISE=${STRIPE_PRICE_ID_ENTERPRISE}
+ - STRIPE_PRICE_ID_ENTERPRISE_OVERAGE=${STRIPE_PRICE_ID_ENTERPRISE_OVERAGE}
+ - STRIPE_TEAM_MONTHLY_PRICE_ID=${STRIPE_TEAM_MONTHLY_PRICE_ID}
+ - IS_TEAM_BILLING_ENABLED=${IS_TEAM_BILLING_ENABLED:-false}
+ - AXIOM_DATASET=${AXIOM_DATASET}
+ - AXIOM_TOKEN=${AXIOM_TOKEN}
+ - LOGGER_BRIDGE_LOG_LEVEL=${LOGGER_BRIDGE_LOG_LEVEL}
+ - DOCS_URL=${DOCS_URL}
+ - GET_LICENSE_KEY_URL=${GET_LICENSE_KEY_URL}
+ depends_on:
+ - database
+ - redis
+
+ # Optional use of Prisma Studio. In production, comment out or remove the section below to prevent unwanted access to your database.
+ studio:
+ image: calcom.docker.scarf.sh/calcom/cal.com
+ restart: always
+ networks:
+ - stack
+ ports:
+ - 5555:5555
+ env_file: .env
+ environment:
+ - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB}
+ - DATABASE_DIRECT_URL=${DATABASE_URL}
+ depends_on:
+ - database
+ command:
+ - npx
+ - prisma
+ - studio
+# END SECTION: Optional use of Prisma Studio.
diff --git a/scripts/replace-placeholder.sh b/scripts/replace-placeholder.sh
new file mode 100644
index 00000000000000..2e85281943168f
--- /dev/null
+++ b/scripts/replace-placeholder.sh
@@ -0,0 +1,13 @@
+FROM=$1
+TO=$2
+
+if [ "${FROM}" = "${TO}" ]; then
+ echo "Nothing to replace, the value is already set to ${TO}."
+ exit 0
+fi
+
+echo "Replacing all statically built instances of $FROM with $TO."
+
+for file in $(egrep -r -l "${FROM}" apps/web/.next/ apps/web/public/); do
+ sed -i -e "s|$FROM|$TO|g" "$file"
+done
diff --git a/scripts/start.sh b/scripts/start.sh
new file mode 100644
index 00000000000000..6e649a97e5be1a
--- /dev/null
+++ b/scripts/start.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -x
+
+# Replace the statically built BUILT_NEXT_PUBLIC_WEBAPP_URL with run-time NEXT_PUBLIC_WEBAPP_URL
+# NOTE: if these values are the same, this will be skipped.
+scripts/replace-placeholder.sh "$BUILT_NEXT_PUBLIC_WEBAPP_URL" "$NEXT_PUBLIC_WEBAPP_URL"
+
+scripts/wait-for-it.sh ${DATABASE_HOST} -- echo "database is up"
+npx prisma migrate deploy --schema /calcom/packages/prisma/schema.prisma
+npx ts-node --transpile-only /calcom/scripts/seed-app-store.ts
+yarn start
diff --git a/scripts/wait-for-it.sh b/scripts/wait-for-it.sh
new file mode 100644
index 00000000000000..08c8d6d9543307
--- /dev/null
+++ b/scripts/wait-for-it.sh
@@ -0,0 +1,184 @@
+#!/bin/sh
+
+# The MIT License (MIT)
+#
+# Copyright (c) 2017 Eficode Oy
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+set -- "$@" -- "$TIMEOUT" "$QUIET" "$PROTOCOL" "$HOST" "$PORT" "$result"
+TIMEOUT=15
+QUIET=0
+# The protocol to make the request with, either "tcp" or "http"
+PROTOCOL="tcp"
+
+echoerr() {
+ if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
+}
+
+usage() {
+ exitcode="$1"
+ cat << USAGE >&2
+Usage:
+ $0 host:port|url [-t timeout] [-- command args]
+ -q | --quiet Do not output any status messages
+ -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
+ -- COMMAND ARGS Execute command with args after the test finishes
+USAGE
+ exit "$exitcode"
+}
+
+wait_for() {
+ case "$PROTOCOL" in
+ tcp)
+ if ! command -v nc >/dev/null; then
+ echoerr 'nc command is missing!'
+ exit 1
+ fi
+ ;;
+ wget)
+ if ! command -v wget >/dev/null; then
+ echoerr 'wget command is missing!'
+ exit 1
+ fi
+ ;;
+ esac
+
+ while :; do
+ case "$PROTOCOL" in
+ tcp)
+ nc -w 1 -z "$HOST" "$PORT" > /dev/null 2>&1
+ ;;
+ http)
+ wget --timeout=1 -q "$HOST" -O /dev/null > /dev/null 2>&1
+ ;;
+ *)
+ echoerr "Unknown protocol '$PROTOCOL'"
+ exit 1
+ ;;
+ esac
+
+ result=$?
+
+ if [ $result -eq 0 ] ; then
+ if [ $# -gt 7 ] ; then
+ for result in $(seq $(($# - 7))); do
+ result=$1
+ shift
+ set -- "$@" "$result"
+ done
+
+ TIMEOUT=$2 QUIET=$3 PROTOCOL=$4 HOST=$5 PORT=$6 result=$7
+ shift 7
+ exec "$@"
+ fi
+ exit 0
+ fi
+
+ if [ "$TIMEOUT" -le 0 ]; then
+ break
+ fi
+ TIMEOUT=$((TIMEOUT - 1))
+
+ sleep 1
+ done
+ echo "Operation timed out" >&2
+ exit 1
+}
+
+while :; do
+ case "$1" in
+ http://*|https://*)
+ HOST="$1"
+ PROTOCOL="http"
+ shift 1
+ ;;
+ *:* )
+ HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
+ PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
+ shift 1
+ ;;
+ -q | --quiet)
+ QUIET=1
+ shift 1
+ ;;
+ -q-*)
+ QUIET=0
+ echoerr "Unknown option: $1"
+ usage 1
+ ;;
+ -q*)
+ QUIET=1
+ result=$1
+ shift 1
+ set -- -"${result#-q}" "$@"
+ ;;
+ -t | --timeout)
+ TIMEOUT="$2"
+ shift 2
+ ;;
+ -t*)
+ TIMEOUT="${1#-t}"
+ shift 1
+ ;;
+ --timeout=*)
+ TIMEOUT="${1#*=}"
+ shift 1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ --help)
+ usage 0
+ ;;
+ -*)
+ QUIET=0
+ echoerr "Unknown option: $1"
+ usage 1
+ ;;
+ *)
+ QUIET=0
+ echoerr "Unknown argument: $1"
+ usage 1
+ ;;
+ esac
+done
+
+if ! [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
+ echoerr "Error: invalid timeout '$TIMEOUT'"
+ usage 3
+fi
+
+case "$PROTOCOL" in
+ tcp)
+ if [ "$HOST" = "" ] || [ "$PORT" = "" ]; then
+ echoerr "Error: you need to provide a host and port to test."
+ usage 2
+ fi
+ ;;
+ http)
+ if [ "$HOST" = "" ]; then
+ echoerr "Error: you need to provide a host to test."
+ usage 2
+ fi
+ ;;
+esac
+
+wait_for "$@"