feat: kamal deployment integration (#2178)

This commit is contained in:
Shubham Palriwala
2024-03-08 17:34:58 +05:30
committed by GitHub
parent a9f35df278
commit 455a061f35
11 changed files with 699 additions and 19 deletions

126
.github/workflows/kamal.yml vendored Normal file
View File

@@ -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

14
.kamal/hooks/post-deploy.sample Executable file
View File

@@ -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"

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "Rebooted Traefik on $KAMAL_HOSTS"

51
.kamal/hooks/pre-build.sample Executable file
View File

@@ -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

47
.kamal/hooks/pre-connect.sample Executable file
View File

@@ -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 ]

109
.kamal/hooks/pre-deploy.sample Executable file
View File

@@ -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

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "Rebooting Traefik on $KAMAL_HOSTS..."

View File

@@ -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

View File

@@ -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:

138
kamal/README.md Normal file
View File

@@ -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 <path-to-your-key>.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

199
kamal/deploy.yml Normal file
View File

@@ -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