diff --git a/.github/workflows/kamal.yml b/.github/workflows/kamal.yml new file mode 100644 index 0000000000..4f45521ca7 --- /dev/null +++ b/.github/workflows/kamal.yml @@ -0,0 +1,126 @@ +name: Kamal Deploy +concurrency: + group: deploy-to-kamal + cancel-in-progress: false + +on: + push: + branches: + - main + +jobs: + Deploy: + runs-on: ubuntu-latest + environment: production + env: + DOCKER_BUILDKIT: 1 + IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }} + WEBAPP_URL: ${{ vars.WEBAPP_URL }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} + ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }} + SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }} + MAIL_FROM: ${{ secrets.MAIL_FROM }} + SMTP_HOST: ${{ secrets.SMTP_HOST }} + SMTP_PORT: ${{ secrets.SMTP_PORT }} + SMTP_SECURE_ENABLED: ${{ secrets.SMTP_SECURE_ENABLED }} + SMTP_USER: ${{ secrets.SMTP_USER }} + SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} + EMAIL_VERIFICATION_DISABLED: ${{ vars.EMAIL_VERIFICATION_DISABLED }} + PASSWORD_RESET_DISABLED: ${{ vars.PASSWORD_RESET_DISABLED }} + SIGNUP_DISABLED: ${{ vars.SIGNUP_DISABLED }} + EMAIL_AUTH_DISABLED: ${{ vars.EMAIL_AUTH_DISABLED }} + INVITE_DISABLED: ${{ vars.INVITE_DISABLED }} + PRIVACY_URL: ${{ vars.PRIVACY_URL }} + TERMS_URL: ${{ vars.TERMS_URL }} + IMPRINT_URL: ${{ vars.IMPRINT_URL }} + GITHUB_ID: ${{ secrets.GITHUB_ID }} + GITHUB_SECRET: ${{ secrets.GITHUB_SECRET }} + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }} + AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }} + AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }} + OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }} + OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }} + OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }} + OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }} + OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }} + CRON_SECRET: ${{ secrets.CRON_SECRET }} + ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }} + NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }} + NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }} + STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} + STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} + GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }} + GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }} + GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }} + AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }} + ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }} + DEFAULT_TEAM_ID: ${{ vars.DEFAULT_TEAM_ID }} + DEFAULT_TEAM_ROLE: ${{ vars.DEFAULT_TEAM_ROLE }} + ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }} + CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }} + CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }} + RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }} + NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }} + NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }} + NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }} + NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }} + NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }} + NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }} + NODE_ENV: production + CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} + CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + S3_REGION: ${{ vars.S3_REGION }} + S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }} + KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + + - name: Install dependencies + run: | + gem install kamal + + - uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Create builder + run: docker buildx create --use --name formbricks-gh-actions-builder + if: steps.buildx.outputs.should_create_builder == 'true' + + - name: Push env variables to Kamal + run: | + kamal() { command kamal "$@" -c kamal/deploy.yml; } + kamal env push + + - name: Run deploy command + run: | + kamal() { command kamal "$@" -c kamal/deploy.yml; } + set +e + DEPLOY_OUTPUT=$(kamal setup 2>&1) + DEPLOY_EXIT_CODE=$? + echo "$DEPLOY_OUTPUT" + if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then + echo "Deployment reported healthy container. Considering as success." + kamal lock release + exit 0 + else + exit $DEPLOY_EXIT_CODE + fi + shell: bash diff --git a/.kamal/hooks/post-deploy.sample b/.kamal/hooks/post-deploy.sample new file mode 100755 index 0000000000..75efafc10e --- /dev/null +++ b/.kamal/hooks/post-deploy.sample @@ -0,0 +1,14 @@ +#!/bin/sh + +# A sample post-deploy hook +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" diff --git a/.kamal/hooks/post-traefik-reboot.sample b/.kamal/hooks/post-traefik-reboot.sample new file mode 100755 index 0000000000..e3d9e3ccbe --- /dev/null +++ b/.kamal/hooks/post-traefik-reboot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Rebooted Traefik on $KAMAL_HOSTS" diff --git a/.kamal/hooks/pre-build.sample b/.kamal/hooks/pre-build.sample new file mode 100755 index 0000000000..f87d81130b --- /dev/null +++ b/.kamal/hooks/pre-build.sample @@ -0,0 +1,51 @@ +#!/bin/sh + +# A sample pre-build hook +# +# Checks: +# 1. We have a clean checkout +# 2. A remote is configured +# 3. The branch has been pushed to the remote +# 4. The version we are deploying matches the remote +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) + +if [ -n "$(git status --porcelain)" ]; then + echo "Git checkout is not clean, aborting..." >&2 + git status --porcelain >&2 + exit 1 +fi + +first_remote=$(git remote) + +if [ -z "$first_remote" ]; then + echo "No git remote set, aborting..." >&2 + exit 1 +fi + +current_branch=$(git branch --show-current) + +if [ -z "$current_branch" ]; then + echo "Not on a git branch, aborting..." >&2 + exit 1 +fi + +remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) + +if [ -z "$remote_head" ]; then + echo "Branch not pushed to remote, aborting..." >&2 + exit 1 +fi + +if [ "$KAMAL_VERSION" != "$remote_head" ]; then + echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 + exit 1 +fi + +exit 0 diff --git a/.kamal/hooks/pre-connect.sample b/.kamal/hooks/pre-connect.sample new file mode 100755 index 0000000000..18e61d7e5a --- /dev/null +++ b/.kamal/hooks/pre-connect.sample @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby + +# A sample pre-connect check +# +# Warms DNS before connecting to hosts in parallel +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +hosts = ENV["KAMAL_HOSTS"].split(",") +results = nil +max = 3 + +elapsed = Benchmark.realtime do + results = hosts.map do |host| + Thread.new do + tries = 1 + + begin + Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) + rescue SocketError + if tries < max + puts "Retrying DNS warmup: #{host}" + tries += 1 + sleep rand + retry + else + puts "DNS warmup failed: #{host}" + host + end + end + + tries + end + end.map(&:value) +end + +retries = results.sum - hosts.size +nopes = results.count { |r| r == max } + +puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] diff --git a/.kamal/hooks/pre-deploy.sample b/.kamal/hooks/pre-deploy.sample new file mode 100755 index 0000000000..1b280c719e --- /dev/null +++ b/.kamal/hooks/pre-deploy.sample @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby + +# A sample pre-deploy hook +# +# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. +# +# Fails unless the combined status is "success" +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_COMMAND +# KAMAL_SUBCOMMAND +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) + +# Only check the build status for production deployments +if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" + exit 0 +end + +require "bundler/inline" + +# true = install gems so this is fast on repeat invocations +gemfile(true, quiet: true) do + source "https://rubygems.org" + + gem "octokit" + gem "faraday-retry" +end + +MAX_ATTEMPTS = 72 +ATTEMPTS_GAP = 10 + +def exit_with_error(message) + $stderr.puts message + exit 1 +end + +class GithubStatusChecks + attr_reader :remote_url, :git_sha, :github_client, :combined_status + + def initialize + @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/") + @git_sha = `git rev-parse HEAD`.strip + @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) + refresh! + end + + def refresh! + @combined_status = github_client.combined_status(remote_url, git_sha) + end + + def state + combined_status[:state] + end + + def first_status_url + first_status = combined_status[:statuses].find { |status| status[:state] == state } + first_status && first_status[:target_url] + end + + def complete_count + combined_status[:statuses].count { |status| status[:state] != "pending"} + end + + def total_count + combined_status[:statuses].count + end + + def current_status + if total_count > 0 + "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." + else + "Build not started..." + end + end +end + + +$stdout.sync = true + +puts "Checking build status..." +attempts = 0 +checks = GithubStatusChecks.new + +begin + loop do + case checks.state + when "success" + puts "Checks passed, see #{checks.first_status_url}" + exit 0 + when "failure" + exit_with_error "Checks failed, see #{checks.first_status_url}" + when "pending" + attempts += 1 + end + + exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS + + puts checks.current_status + sleep(ATTEMPTS_GAP) + checks.refresh! + end +rescue Octokit::NotFound + exit_with_error "Build status could not be found" +end diff --git a/.kamal/hooks/pre-traefik-reboot.sample b/.kamal/hooks/pre-traefik-reboot.sample new file mode 100755 index 0000000000..8cfda6d915 --- /dev/null +++ b/.kamal/hooks/pre-traefik-reboot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Rebooting Traefik on $KAMAL_HOSTS..." diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 8ec43733be..d9de404d14 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -26,18 +26,13 @@ RUN apk update && apk add --no-cache g++ cmake make gcc python3 openssl-dev jq # Install Supercronic (cron for containers without super user privileges) RUN apk add --no-cache curl \ && curl -fsSLo /tmp/supercronic \ - "https://github.com/aptible/supercronic/releases/download/v0.2.27/supercronic-linux-amd64" \ + "https://github.com/aptible/supercronic/releases/download/v0.2.27/supercronic-linux-amd64" \ && chmod +x /tmp/supercronic -# Set environment variables -ARG DATABASE_URL -ENV DATABASE_URL=$DATABASE_URL - -ARG NEXTAUTH_SECRET -ENV NEXTAUTH_SECRET=$NEXTAUTH_SECRET - -ARG ENCRYPTION_KEY -ENV ENCRYPTION_KEY=$ENCRYPTION_KEY +# Set hardcoded environment variables +ENV DATABASE_URL="postgresql://placeholder:for@build:5432/gets_overwritten_at_runtime?schema=public" +ENV NEXTAUTH_SECRET="placeholder_for_next_auth_of_64_chars_get_overwritten_at_runtime" +ENV ENCRYPTION_KEY="placeholder_for_build_key_of_64_chars_get_overwritten_at_runtime" ARG NEXT_PUBLIC_SENTRY_DSN @@ -99,9 +94,9 @@ VOLUME /home/nextjs/apps/web/uploads/ CMD PRISMA_VERSION=$(cat prisma_version.txt) && \ supercronic -quiet /app/docker/cronjobs & \ if [ "$NEXTAUTH_SECRET" != "RANDOM_STRING" ]; then \ - pnpm dlx prisma@$PRISMA_VERSION migrate deploy && \ - exec node apps/web/server.js; \ + pnpm dlx prisma@$PRISMA_VERSION migrate deploy && \ + exec node apps/web/server.js; \ else \ - echo "ERROR: Please set a value for NEXTAUTH_SECRET in your docker compose variables!" >&2; \ - exit 1; \ + echo "ERROR: Please set a value for NEXTAUTH_SECRET in your docker compose variables!" >&2; \ + exit 1; \ fi \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3f8583ea09..c3ed356eab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -80,11 +80,6 @@ services: build: context: . dockerfile: ./apps/web/Dockerfile - args: - DATABASE_URL: *database_url - NEXTAUTH_SECRET: *nextauth_secret - ENCRYPTION_KEY: *encryption_key - depends_on: - postgres ports: diff --git a/kamal/README.md b/kamal/README.md new file mode 100644 index 0000000000..6d1c9dbb6f --- /dev/null +++ b/kamal/README.md @@ -0,0 +1,138 @@ +# Kamal Setup + +1. Initiate a Linux instance & Get it's + - Public IP address + - SSH credentials + +2. Edit the IP address & SSH config in Kamal `deploy.yml` file. +3. Test you SSH status with: + + ```sh + kamal lock status + ``` + +4. If the above returns a non-zero error code, run the below: + + ```sh + eval `ssh-agent -s` + ssh-add .pem + ``` + +5. Now test the SSH status again: + + ```sh + kamal lock status + ``` + + This should now run successfully & exit with 0. + +6. Generate a Classic Personal Access Token for `container:write` & `container:read` for your Image Registry (DockerHub, GHCR, etc.) & add it to your environment variables (.env file). Also update the Registry details in the `deploy.yml`. + +7. If your SSH user is a non-root user, run the below command to add the user to the docker group: + + ```sh + sudo usermod -aG docker ${USER} + ``` + + > Note: The above needs to be ran on the Cloud VM. There is an open Issue on Kamal for the same [here](https://github.com/basecamp/kamal/issues/405) + +> Run the below for SSL config the first time +```sh +sudo mkdir -p /letsencrypt && sudo touch /letsencrypt/acme.json && sudo chmod 600 /letsencrypt/acme.json +``` + +8. Make sure you have docker buildx locally on your machine where you run the kamal CLI from! + +9. Voila! You are all set to deploy your application to the cloud with Kamal! 🚀 + + ```sh + kamal setup -c kamal/deploy.yml + ``` + + This will setup the cloud VM with all the necessary tools & dependencies to run your application. + +> Make sure to run `kamal env push` before a `kamal deploy` to push the latest environment variables to the cloud VM. + +### Debugging for Kamal + +- If you run into an error such as: + + ```sh + failed to solve: cannot copy to non-directory: + ``` + + Then simply run `pnpm clean` & try again. + +- Make sure your Database accepts connection from the cloud VM. You can do this by adding the VM's IP address to the `Allowed Hosts` in your Database settings. + +- If you get an error such as: + + ```sh + Lock failed: failed to acquire lock: lockfile already exists + ``` + + Then simply run `kamal lock release -c kamal/deploy.yml` & try again. + +- If you run into: + ```sh + No config found + ``` + + Then simply add the following at the end of the command: `-c kamal/deploy.yml` + +For further details, refer to the [Kamal Documentation](https://kamal-deploy.org/docs/configuration) or reach out to us on our [Discord](https://formbricks.com/discord) + +# Rollback to a Previous Version + +```sh +kamal rollback [git_commit_hash_to_rollback_to] -c kamal/deploy.yml +``` + +## View Formbricks Server logs with Kamal + +```sh +kamal app logs -c kamal/deploy.yml +``` + +# Configure Memory Metrics on AWS using CW Agent + +1. Install the CloudWatch Agent on the EC2 instance + +```sh +wget https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb +sudo dpkg -i -E ./amazon-cloudwatch-agent.deb +``` + +2. Attach the IAM role of `CloudWatchFullAccessv2` to the EC2 instance + +3. Edit the CloudWatch Agent config file + +```sh +sudo nano /opt/aws/amazon-cloudwatch-agent/bin/config.json +``` + +4. Add the below config to the `config.json` file + +```json +{ + "metrics": { + "metrics_collected": { + "mem": { + "measurement": ["mem_used_percent"], + "metrics_collection_interval": 60 + } + }, + "append_dimensions": { + "InstanceId": "${aws:InstanceId}" + } + } +} +``` + +5. Fetch the config & start the CloudWatch Agent + +```sh +sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s +``` + +6. Now go to Cloudwatch > Metrics > All Metrics > Custom Namespaces > CWAgent > InstanceId > {InstanceId} > Tick the Checkbox next to it > Graph above will be updated diff --git a/kamal/deploy.yml b/kamal/deploy.yml new file mode 100644 index 0000000000..a07205b5c7 --- /dev/null +++ b/kamal/deploy.yml @@ -0,0 +1,199 @@ +# Name of your application. Used to uniquely configure containers. +service: formbricks-kamal + +# Name of the container image. +image: formbricks/formbricks-cloud + +# Deploy to these servers. +servers: + web: # Use a named role, so it can be used as entrypoint by Traefik + hosts: + - 18.196.187.144 + labels: + traefik.http.routers.formbricks-kamal.entrypoints: websecure + traefik.http.routers.formbricks-kamal.rule: Host(`kamal.formbricks.com`) + traefik.http.routers.formbricks-kamal.tls.certresolver: letsencrypt + +# Credentials for your image host. +registry: + # Specify the registry server, if you're not using Docker Hub + server: ghcr.io + username: mattinannt + + # Always use an access token rather than real password when possible. + password: + - KAMAL_REGISTRY_PASSWORD + +# Inject ENV variables into containers (secrets come from .env). +# Remember to run `kamal env push` after making changes! +env: + # clear: + # DB_HOST: 192.168.0.2 + secret: + - IS_FORMBRICKS_CLOUD + - WEBAPP_URL + - DATABASE_URL + - NEXTAUTH_SECRET + - ENCRYPTION_KEY + - SHORT_URL_BASE + - MAIL_FROM + - SMTP_HOST + - SMTP_PORT + - SMTP_SECURE_ENABLED + - SMTP_USER + - SMTP_PASSWORD + - EMAIL_VERIFICATION_DISABLED + - PASSWORD_RESET_DISABLED + - SIGNUP_DISABLED + - EMAIL_AUTH_DISABLED + - INVITE_DISABLED + - PRIVACY_URL + - TERMS_URL + - IMPRINT_URL + - GITHUB_ID + - GITHUB_SECRET + - GOOGLE_CLIENT_ID + - GOOGLE_CLIENT_SECRET + - AZUREAD_CLIENT_ID + - AZUREAD_CLIENT_SECRET + - AZUREAD_TENANT_ID + - OIDC_CLIENT_ID + - OIDC_CLIENT_SECRET + - OIDC_ISSUER + - OIDC_DISPLAY_NAME + - OIDC_SIGNING_ALGORITHM + - CRON_SECRET + - ASSET_PREFIX_URL + - NOTION_OAUTH_CLIENT_ID + - NOTION_OAUTH_CLIENT_SECRET + - STRIPE_SECRET_KEY + - STRIPE_WEBHOOK_SECRET + - GOOGLE_SHEETS_CLIENT_ID + - GOOGLE_SHEETS_CLIENT_SECRET + - GOOGLE_SHEETS_REDIRECT_URL + - AIRTABLE_CLIENT_ID + - ENTERPRISE_LICENSE_KEY + - DEFAULT_TEAM_ID + - DEFAULT_TEAM_ROLE + - ONBOARDING_DISABLED + - CUSTOMER_IO_API_KEY + - CUSTOMER_IO_SITE_ID + - RATE_LIMITING_DISABLED + - NEXT_PUBLIC_POSTHOG_API_KEY + - NEXT_PUBLIC_POSTHOG_API_HOST + - NEXT_PUBLIC_FORMBRICKS_API_HOST + - NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID + - NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID + - NEXT_PUBLIC_SENTRY_DSN + - CLOUDFLARE_EMAIL + - CLOUDFLARE_DNS_API_TOKEN + - S3_ACCESS_KEY + - S3_SECRET_KEY + - S3_REGION + - S3_BUCKET_NAME + +# Use a different ssh user than root +ssh: + user: ubuntu + +# Configure builder setup. +builder: + context: . + dockerfile: ./apps/web/Dockerfile + args: + - NEXT_PUBLIC_POSTHOG_API_KEY + - NEXT_PUBLIC_POSTHOG_API_HOST + - NEXT_PUBLIC_FORMBRICKS_API_HOST + - NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID + - NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID + - NEXT_PUBLIC_SENTRY_DSN + - ASSET_PREFIX_URL + + multiarch: false + cache: + type: registry + options: mode=max,image-manifest=true,oci-mediatypes=true +# secrets: +# - GITHUB_TOKEN +# remote: +# arch: amd64 +# host: ssh://app@192.168.0.1 + +traefik: + options: + publish: + - "443:443" + volume: + - "/letsencrypt/acme.json:/letsencrypt/acme.json" # To save the configuration file. + args: + entryPoints.web.address: ":80" + entryPoints.websecure.address: ":443" + entryPoints.web.http.redirections.entryPoint.to: websecure + entryPoints.web.http.redirections.entryPoint.scheme: https + entryPoints.web.http.redirections.entrypoint.permanent: true + entrypoints.websecure.http.tls: true + entrypoints.websecure.http.tls.domains[0].main: "kamal.formbricks.com" + entrypoints.websecure.http.tls.domains[0].sans: "*.formbricks.com" + certificatesResolvers.letsencrypt.acme.email: "shubham@formbricks.com" + certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json" + certificatesresolvers.letsencrypt.acme.dnschallenge.provider: cloudflare + env: + secret: + - CLOUDFLARE_DNS_API_TOKEN + - CLOUDFLARE_EMAIL + +# Use accessory services (secrets come from .env). +# accessories: +# db: +# image: mysql:8.0 +# host: 192.168.0.2 +# port: 3306 +# env: +# clear: +# MYSQL_ROOT_HOST: '%' +# secret: +# - MYSQL_ROOT_PASSWORD +# files: +# - config/mysql/production.cnf:/etc/mysql/my.cnf +# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql +# directories: +# - data:/var/lib/mysql +# redis: +# image: redis:7.0 +# host: 192.168.0.2 +# port: 6379 +# directories: +# - data:/data + +# Configure custom arguments for Traefik +# traefik: +# args: +# accesslog: true +# accesslog.format: json + +healthcheck: + path: /health + port: 3000 + max_attempts: 7 + interval: 20s +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +# asset_path: /rails/public/assets + +# Configure rolling deploys by setting a wait time between batches of restarts. +# boot: +# limit: 10 # Can also specify as a percentage of total hosts, such as "25%" +# wait: 2 + +# Configure the role used to determine the primary_host. This host takes +# deploy locks, runs health checks during the deploy, and follow logs, etc. +# +# Caution: there's no support for role renaming yet, so be careful to cleanup +# the previous role on the deployed hosts. +# primary_role: web + +# Controls if we abort when see a role with no hosts. Disabling this may be +# useful for more complex deploy configurations. +# +# allow_empty_roles: false