-
Notifications
You must be signed in to change notification settings - Fork 563
Gauzy Self‐Hosted Setup in DigitalOcean
-
Fork Gauzy Platform repo (private or public).
-
Run PostgreSQL DB in DigitalOcean (DO) and create an empty DB. Take connection parameters and add them to your GitHub Secrets into corresponding env vars:
- DB_HOST / DB_HOST_PUBLIC (Hostname of DB)
- DB_PASS (Password for DB user)
- DB_PORT (Database Port)
- DB_SSL_MODE (value should be
true
if your DB provider requires SSL connectivity) - DB_TYPE (e.g. "postgres" for PostgreSQL DB)
- DB_USER (Username used to connect to DB)
We recommend running the latest PostgreSQL (16.x) and also using connection pooling enabled for production workloads. Make sure you create a PostgreSQL cluster in the SAME region as your Kubernetes cluster.
You need to create a special env var DB_CA_CERT
for DO DB CA Certificate, use the Bash command below to get a value for it (assuming you saved the CA Cert file in the ca-certificate.crt
file):
base64 ca-certificate.crt
- Create Kubernetes cluster (k8s) in DO (latest version). We recommend running at least 2 node clusters, each node with 8Gb RAM or more. Make sure it runs in the same region as your DB and make sure you allow connection to DB from that cluster (it's a setting of PostgreSQL DB in DO). Write down the name of the cluster you created, you will need to update it in our codebase.
You need to define the env var that stores the DO access token: DIGITALOCEAN_ACCESS_TOKEN
as it's used in the installation/configuration of doctl
CLI:
- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
(see for example deploy-do-demo.yml
file in the .github/workflows
folder)
-
Add an SSL Certificate for your domain to "Networking" in DO. We recommend using Cloudflare to generate such a server certificate (free of charge)
-
Check GitHub actions that deploy docker containers to the k8s cluster in DigitalOcean (DO), e.g. https://github.com/ever-co/ever-gauzy/blob/develop/.github/workflows/deploy-do-prod.yml and note all names of env vars that needs to be created as GitHub Secrets (e.g. DB_USER, DB_PASSWORD, and so on).
-
Check k8s Manifest in https://github.com/ever-co/ever-gauzy/blob/develop/.deploy/k8s/k8s-manifest.prod.yaml. You mostly need to replace some parameters, such as domain names, certificate IDs in DO, etc. In addition, you need to replace our docker images for API and UI (e.g. "registry.digitalocean.com/ever/gauzy-api:latest") with your own images (e.g. if you copy to your own registry, recommended for production workloads) or use our public images (see https://github.com/orgs/ever-co/packages?repo_name=ever-gauzy)
-
After you defined all Secrets in GitHub and changed manifests/workflows described above, you need to push your changes to the
develop
branch (if it's your default one) and next to themaster
branch to make sure deployment proceeds. As a result, you should be able to see Pods running in the k8s cluster: for API and UI (frontend).
We estimate it should take from a few hours to about 8 hours of work for a senior DevOps Engineer (depending on many factors) and if you want us to help with that setup, feel free to email to [email protected]. The cost will be calculated on the exact amount of hour setup takes on the 35$ per hour rate.
Setup on the DigitalOcean App Platform
-
Make sure GitHub Secret
DIGITALOCEAN_ACCESS_TOKEN
is set to your DigitalOcean access token that has enough privileges to create/update DigitalOcean App Platform applications. -
Our existing GitHub actions are used to create 3 separate "Apps" in DigitalOcean, one for each environment, e.g.
ever-gauzy-demo
,ever-gauzy-stage
andever-gauzy-prod
-
Check .github/workflows/deploy-do-app-platform-demo.yml, .github/workflows/deploy-do-app-platform-stage.yml and .github/workflows/deploy-do-app-platform-prod.yml
-
Each GitHub action creates or updates a separate DO App and assigns a custom Domain name to each env (see APP_DOMAIN env var)
-
You can define the type of instance (size) in the
INSTANCE_SIZE
env var (see possible values in the following link) and count in theINSTANCE_COUNT
env var. We recommend using 1 instance in thedemo
env and at least 2 instances in thestage
env. Make sure you are using aprofessional
instance size and at least 2 instances forprod
environment. -
Important to make sure
DEMO
env var is set tofalse
forprod
environment. We also recommend to setADMIN_PASSWORD_RESET
tofalse
for production. -
As with any other setup of our platform, make sure
API_BASE_URL
is set to the URL on which you would like to have API exposed andCLIENT_BASE_URL
should be set to the URL on which you will have client-side UI loaded. -
You can also research .do/app.yaml file that is used as a template to define the DO App Platform application. Important to note that you will have to configure
services/image/registry_type
,services/image/registry
, andservices/image/repository
to use your own DigitalOcean or DockerHub Container Registry, please refer to DO documentation about those parameters.
We support the deployment of the Ever Gauzy Platform into 3 separate droplets in DigitalOcean, one Droplet for each environment (demo, stage, prod)
-
deploy-do-droplet-pre-demo.yml
and.deploy/ssh/docker-compose.api.demo.pre.yml
- those prepare DO Droplet #1 with Demo env by installing and configured nginx proxy with either CF or LetsEncrypt certificate, depending on the env varINGRESS_CERT_TYPE=letsencrypt | cloudflare
set value -
deploy-do-droplet-pre-stage.yml
and.deploy/ssh/docker-compose.api.stage.pre.yml
- those prepare DO Droplet #2 with Stage env by installing and configured nginx proxy with either CF or LetsEncrypt certificate, depending on the env varINGRESS_CERT_TYPE=letsencrypt | cloudflare
set value -
deploy-do-droplet-pre-prod.yml
and.deploy/ssh/docker-compose.api.prod.pre.yml
- those prepare DO Droplet #3 with Production environment by installing and configured nginx proxy with either CF or LetsEncrypt certificate, depending on the env varINGRESS_CERT_TYPE=letsencrypt | cloudflare
set value -
deploy-do-droplet-demo.yml
- deploy DEMO env containers using Docker Compose file.deploy/ssh/docker-compose.api.demo.template.yml
to DO Droplet #1 -
deploy-do-droplet-stage.yml
- deploy STAGE env containers using Docker Compose file.deploy/ssh/docker-compose.api.stage.template.yml
to DO Droplet #2 -
deploy-do-droplet-prod.yml
- deploy PROD env containers using Docker Compose file.deploy/ssh/docker-compose.api.prod.template.yml
to DO Droplet #3
SSH keys provide a secure way of logging into a remote server without needing to enter a password each time. Here's how you can set up SSH keys on your local machine and then add the public key to your DigitalOcean droplet.
-
Open your terminal or command prompt.
-
Use the following command to generate an SSH key pair:
ssh-keygen -t rsa -b 4096 -C "[email protected]"
Replace "[email protected]"
with your email address. This command will generate a new SSH key using the provided email.
-
You will be prompted to enter a file to save the key. By default, it will save to
~/.ssh/id_rsa
. Press Enter to confirm or specify a different location. -
You will then be prompted to enter a passphrase. It is recommended to use a passphrase, but you can also leave it blank for no passphrase. Press Enter after entering your passphrase.
-
The SSH key pair will be generated. You will see output similar to:
Your identification has been saved in /home/your_username/.ssh/id_rsa
Your public key has been saved in /home/your_username/.ssh/id_rsa.pub
-
Log in to your DigitalOcean account.
-
Navigate to the "Droplets" section.
-
Click on the droplet you want to connect to.
-
In the droplet's dashboard, click on the "Access" tab.
-
Scroll down to the "SSH keys" section and click on "Add SSH Key".
-
Copy the contents of your public key. You can do this by running:
cat ~/.ssh/id_rsa.pub
- Paste the public key into the provided text box and click "Add SSH Key".
Now that your SSH key is added to your droplet, you can SSH into it from your terminal.
-
Open your terminal.
-
Use the following command to SSH into your droplet:
ssh username@droplet_ip_address
Replace username
with your username on the droplet, and droplet_ip_address
with the IP address of your droplet.
-
If you set a passphrase for your SSH key, you will be prompted to enter it.
-
You are now logged into your DigitalOcean droplet via SSH.
Setup Docker-compose(Gauzy Api and nginx proxy) using CloudFlare own Certificate and Letsencrypt self assigned certificate
Create a directory with-cloudflare
and within the directory create another directory called demo
, then create a docker-compose file for the gauzy api
docker-compose.api.demo.template.yml
version: '3.8'
services:
api:
#container_name: api-${ENV_NAME}
image: ghcr.io/ever-co/gauzy-api-demo:latest
deploy:
mode: replicated
replicas: 2
environment:
API_HOST: '0.0.0.0'
DEMO: '${DEMO:-true}'
NODE_ENV: '${NODE_ENV:-development}'
ADMIN_PASSWORD_RESET: '${ADMIN_PASSWORD_RESET:-}'
API_BASE_URL: '${API_BASE_URL:-http://localhost:3000}'
CLIENT_BASE_URL: '${CLIENT_BASE_URL:-http://localhost:4200}'
DB_TYPE: '${DB_TYPE:-better-sqlite3}'
DB_URI: '${DB_URI:-}'
DB_HOST: '${DB_HOST:-}'
DB_USER: '${DB_USER:-}'
DB_PASS: '${DB_PASS:-}'
DB_NAME: '${DB_NAME:-}'
DB_PORT: '${DB_PORT:-}'
DB_CA_CERT: '${DB_CA_CERT:-}'
DB_SSL_MODE: '${DB_SSL_MODE:-}'
DB_POOL_SIZE: '${DB_POOL_SIZE:-}'
DB_POOL_SIZE_KNEX: '${DB_POOL_SIZE_KNEX:-}'
REDIS_ENABLED: '${REDIS_ENABLED:-}'
REDIS_URL: '${REDIS_URL:-}'
CLOUD_PROVIDER: 'DO'
SENTRY_DSN: '${SENTRY_DSN:-}'
SENTRY_TRACES_SAMPLE_RATE: '${SENTRY_TRACES_SAMPLE_RATE:-}'
SENTRY_PROFILE_SAMPLE_RATE: '${SENTRY_PROFILE_SAMPLE_RATE:-}'
SENTRY_HTTP_TRACING_ENABLED: '${SENTRY_HTTP_TRACING_ENABLED:-}'
SENTRY_POSTGRES_TRACKING_ENABLED: '${SENTRY_POSTGRES_TRACKING_ENABLED:-}'
SENTRY_PROFILING_ENABLED: '${SENTRY_PROFILING_ENABLED:-}'
AWS_ACCESS_KEY_ID: '${AWS_ACCESS_KEY_ID:-}'
AWS_SECRET_ACCESS_KEY: '${AWS_SECRET_ACCESS_KEY:-}'
AWS_REGION: '${AWS_REGION:-}'
AWS_S3_BUCKET: '${AWS_S3_BUCKET:-}'
WASABI_ACCESS_KEY_ID: '${WASABI_ACCESS_KEY_ID:-}'
WASABI_SECRET_ACCESS_KEY: '${WASABI_SECRET_ACCESS_KEY:-}'
WASABI_REGION: '${WASABI_REGION:-}'
WASABI_SERVICE_URL: '${WASABI_SERVICE_URL:-}'
WASABI_S3_BUCKET: '${WASABI_S3_BUCKET:-}'
EXPRESS_SESSION_SECRET: '${EXPRESS_SESSION_SECRET:-}'
JWT_SECRET: '${JWT_SECRET:-}'
JWT_REFRESH_TOKEN_SECRET: '${JWT_REFRESH_TOKEN_SECRET:-}'
JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${JWT_REFRESH_TOKEN_EXPIRATION_TIME:-}'
CLOUDINARY_API_KEY: '${CLOUDINARY_API_KEY:-}'
CLOUDINARY_API_SECRET: '${CLOUDINARY_API_SECRET:-}'
CLOUDINARY_CLOUD_NAME: '${CLOUDINARY_CLOUD_NAME:-}'
MAIL_FROM_ADDRESS: '${MAIL_FROM_ADDRESS:-}'
MAIL_HOST: '${MAIL_HOST:-}'
MAIL_PORT: '${MAIL_PORT:-}'
MAIL_USERNAME: '${MAIL_USERNAME:-}'
MAIL_PASSWORD: '${MAIL_PASSWORD:-}'
ALLOW_SUPER_ADMIN_ROLE: '${ALLOW_SUPER_ADMIN_ROLE:-}'
GOOGLE_CLIENT_ID: '${GOOGLE_CLIENT_ID:-}'
GOOGLE_CLIENT_SECRET: '${GOOGLE_CLIENT_SECRET:-}'
GOOGLE_CALLBACK_URL: '${GOOGLE_CALLBACK_URL:-}'
FACEBOOK_CLIENT_ID: '${FACEBOOK_CLIENT_ID:-}'
FACEBOOK_CLIENT_SECRET: '${FACEBOOK_CLIENT_SECRET:-}'
FACEBOOK_GRAPH_VERSION: '${FACEBOOK_GRAPH_VERSION:-}'
FACEBOOK_CALLBACK_URL: '${FACEBOOK_CALLBACK_URL:-}'
INTEGRATED_USER_DEFAULT_PASS: '${INTEGRATED_USER_DEFAULT_PASS:-}'
UPWORK_REDIRECT_URL: '${UPWORK_REDIRECT_URL:-}'
FILE_PROVIDER: '${FILE_PROVIDER:-}'
GAUZY_AI_GRAPHQL_ENDPOINT: '${GAUZY_AI_GRAPHQL_ENDPOINT:-}'
GAUZY_AI_REST_ENDPOINT: '${GAUZY_AI_REST_ENDPOINT:-}'
UNLEASH_APP_NAME: '${UNLEASH_APP_NAME:-}'
UNLEASH_API_URL: '${UNLEASH_API_URL:-}'
UNLEASH_INSTANCE_ID: '${UNLEASH_INSTANCE_ID:-}'
UNLEASH_REFRESH_INTERVAL: '${UNLEASH_REFRESH_INTERVAL:-}'
UNLEASH_METRICS_INTERVAL: '${UNLEASH_METRICS_INTERVAL:-}'
UNLEASH_API_KEY: '${UNLEASH_API_KEY:-}'
PM2_MACHINE_NAME: '${PM2_MACHINE_NAME:-}'
PM2_SECRET_KEY: '${PM2_SECRET_KEY:-}'
PM2_PUBLIC_KEY: '${PM2_PUBLIC_KEY:-}'
JITSU_SERVER_URL: '${JITSU_SERVER_URL:-}'
JITSU_SERVER_WRITE_KEY: '${JITSU_SERVER_WRITE_KEY:-}'
OTEL_ENABLED: '${OTEL_ENABLED:-}'
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}'
OTEL_EXPORTER_OTLP_HEADERS: '${OTEL_EXPORTER_OTLP_HEADERS:-}'
GAUZY_GITHUB_CLIENT_ID: '${GAUZY_GITHUB_CLIENT_ID:-}'
GAUZY_GITHUB_CLIENT_SECRET: '${GAUZY_GITHUB_CLIENT_SECRET:-}'
GAUZY_GITHUB_APP_PRIVATE_KEY: '${GAUZY_GITHUB_APP_PRIVATE_KEY:-}'
GAUZY_GITHUB_WEBHOOK_URL: '${GAUZY_GITHUB_WEBHOOK_URL:-}'
GAUZY_GITHUB_WEBHOOK_SECRET: '${GAUZY_GITHUB_WEBHOOK_SECRET:-}'
GAUZY_GITHUB_APP_NAME: '${GAUZY_GITHUB_APP_NAME:-}'
GAUZY_GITHUB_REDIRECT_URL: '${GAUZY_GITHUB_REDIRECT_URL:-}'
GAUZY_GITHUB_POST_INSTALL_URL: '${GAUZY_GITHUB_POST_INSTALL_URL:-}'
GAUZY_GITHUB_APP_ID: '${GAUZY_GITHUB_APP_ID:-}'
GAUZY_GITHUB_OAUTH_CLIENT_ID: '${GAUZY_GITHUB_OAUTH_CLIENT_ID:-}'
GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${GAUZY_GITHUB_OAUTH_CLIENT_SECRET:-}'
GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${GAUZY_GITHUB_OAUTH_CALLBACK_URL:-}'
JITSU_BROWSER_URL: '${JITSU_BROWSER_URL:-}'
JITSU_BROWSER_WRITE_KEY: '${JITSU_BROWSER_WRITE_KEY:-}'
MAGIC_CODE_EXPIRATION_TIME: '${MAGIC_CODE_EXPIRATION_TIME:-}'
APP_NAME: '${APP_NAME:-}'
APP_LOGO: '${APP_LOGO:-}'
APP_SIGNATURE: '${APP_SIGNATURE:-}'
APP_LINK: '${APP_LINK:-}'
APP_EMAIL_CONFIRMATION_URL: '${APP_EMAIL_CONFIRMATION_URL:-}'
APP_MAGIC_SIGN_URL: '${APP_MAGIC_SIGN_URL:-}'
COMPANY_LINK: '${COMPANY_LINK:-}'
COMPANY_NAME: '${COMPANY_NAME:-}'
entrypoint: './entrypoint.prod.sh'
command: ['node', 'main.js']
restart: on-failure
ports:
- '3000'
# '3000-3001:'${API_PORT:-3000}''
networks:
- overlay
volumes:
certificates: {}
networks:
overlay:
driver: bridge
within the same demo
directory create another docker-compose file for the nginx proxy docker-compose.api.demo.cloudflare.pre.yml
version: '3.8'
services:
nginx:
image: nginx:latest
volumes:
- ./nginx.demo.pre.cloudflare.conf:/etc/nginx/nginx.conf:ro
- ./ingress.api.crt:/etc/nginx/ssl/fullchain.pem
- ./ingress.api.key:/etc/nginx/ssl/privkey.pem
restart: unless-stopped
ports:
- "80:80"
- "443:443"
networks:
- with-cloudflare_overlay
volumes:
certificates: {}
networks:
with-cloudflare_overlay:
external: true
Then create the nginx config file in the same directory which will be mounted to the nginx service volume nginx.demo.pre.cloudflare.conf
user nginx;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name apidemodt.gauzy.co;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name apidemodt.gauzy.co;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location / {
proxy_pass http://api:3000;
}
}
}
NB: the server_name must match the domain which has been configured with the certifcates
Create a directory with-letsencrypt
and within the directory create another directory demo
, then create a docker-compose file for the gauzy api
docker-compose.api.demo.template.yml
version: '3.8'
services:
api:
#container_name: api-${ENV_NAME}
image: ghcr.io/ever-co/gauzy-api-demo:latest
deploy:
mode: replicated
replicas: 2
environment:
API_HOST: '0.0.0.0'
DEMO: '${DEMO:-true}'
NODE_ENV: '${NODE_ENV:-development}'
ADMIN_PASSWORD_RESET: '${ADMIN_PASSWORD_RESET:-}'
API_BASE_URL: '${API_BASE_URL:-http://localhost:3000}'
CLIENT_BASE_URL: '${CLIENT_BASE_URL:-http://localhost:4200}'
DB_TYPE: '${DB_TYPE:-better-sqlite3}'
DB_URI: '${DB_URI:-}'
DB_HOST: '${DB_HOST:-}'
DB_USER: '${DB_USER:-}'
DB_PASS: '${DB_PASS:-}'
DB_NAME: '${DB_NAME:-}'
DB_PORT: '${DB_PORT:-}'
DB_CA_CERT: '${DB_CA_CERT:-}'
DB_SSL_MODE: '${DB_SSL_MODE:-}'
DB_POOL_SIZE: '${DB_POOL_SIZE:-}'
DB_POOL_SIZE_KNEX: '${DB_POOL_SIZE_KNEX:-}'
REDIS_ENABLED: '${REDIS_ENABLED:-}'
REDIS_URL: '${REDIS_URL:-}'
CLOUD_PROVIDER: 'DO'
SENTRY_DSN: '${SENTRY_DSN:-}'
SENTRY_TRACES_SAMPLE_RATE: '${SENTRY_TRACES_SAMPLE_RATE:-}'
SENTRY_PROFILE_SAMPLE_RATE: '${SENTRY_PROFILE_SAMPLE_RATE:-}'
SENTRY_HTTP_TRACING_ENABLED: '${SENTRY_HTTP_TRACING_ENABLED:-}'
SENTRY_POSTGRES_TRACKING_ENABLED: '${SENTRY_POSTGRES_TRACKING_ENABLED:-}'
SENTRY_PROFILING_ENABLED: '${SENTRY_PROFILING_ENABLED:-}'
AWS_ACCESS_KEY_ID: '${AWS_ACCESS_KEY_ID:-}'
AWS_SECRET_ACCESS_KEY: '${AWS_SECRET_ACCESS_KEY:-}'
AWS_REGION: '${AWS_REGION:-}'
AWS_S3_BUCKET: '${AWS_S3_BUCKET:-}'
WASABI_ACCESS_KEY_ID: '${WASABI_ACCESS_KEY_ID:-}'
WASABI_SECRET_ACCESS_KEY: '${WASABI_SECRET_ACCESS_KEY:-}'
WASABI_REGION: '${WASABI_REGION:-}'
WASABI_SERVICE_URL: '${WASABI_SERVICE_URL:-}'
WASABI_S3_BUCKET: '${WASABI_S3_BUCKET:-}'
EXPRESS_SESSION_SECRET: '${EXPRESS_SESSION_SECRET:-}'
JWT_SECRET: '${JWT_SECRET:-}'
JWT_REFRESH_TOKEN_SECRET: '${JWT_REFRESH_TOKEN_SECRET:-}'
JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${JWT_REFRESH_TOKEN_EXPIRATION_TIME:-}'
CLOUDINARY_API_KEY: '${CLOUDINARY_API_KEY:-}'
CLOUDINARY_API_SECRET: '${CLOUDINARY_API_SECRET:-}'
CLOUDINARY_CLOUD_NAME: '${CLOUDINARY_CLOUD_NAME:-}'
MAIL_FROM_ADDRESS: '${MAIL_FROM_ADDRESS:-}'
MAIL_HOST: '${MAIL_HOST:-}'
MAIL_PORT: '${MAIL_PORT:-}'
MAIL_USERNAME: '${MAIL_USERNAME:-}'
MAIL_PASSWORD: '${MAIL_PASSWORD:-}'
ALLOW_SUPER_ADMIN_ROLE: '${ALLOW_SUPER_ADMIN_ROLE:-}'
GOOGLE_CLIENT_ID: '${GOOGLE_CLIENT_ID:-}'
GOOGLE_CLIENT_SECRET: '${GOOGLE_CLIENT_SECRET:-}'
GOOGLE_CALLBACK_URL: '${GOOGLE_CALLBACK_URL:-}'
FACEBOOK_CLIENT_ID: '${FACEBOOK_CLIENT_ID:-}'
FACEBOOK_CLIENT_SECRET: '${FACEBOOK_CLIENT_SECRET:-}'
FACEBOOK_GRAPH_VERSION: '${FACEBOOK_GRAPH_VERSION:-}'
FACEBOOK_CALLBACK_URL: '${FACEBOOK_CALLBACK_URL:-}'
INTEGRATED_USER_DEFAULT_PASS: '${INTEGRATED_USER_DEFAULT_PASS:-}'
UPWORK_REDIRECT_URL: '${UPWORK_REDIRECT_URL:-}'
FILE_PROVIDER: '${FILE_PROVIDER:-}'
GAUZY_AI_GRAPHQL_ENDPOINT: '${GAUZY_AI_GRAPHQL_ENDPOINT:-}'
GAUZY_AI_REST_ENDPOINT: '${GAUZY_AI_REST_ENDPOINT:-}'
UNLEASH_APP_NAME: '${UNLEASH_APP_NAME:-}'
UNLEASH_API_URL: '${UNLEASH_API_URL:-}'
UNLEASH_INSTANCE_ID: '${UNLEASH_INSTANCE_ID:-}'
UNLEASH_REFRESH_INTERVAL: '${UNLEASH_REFRESH_INTERVAL:-}'
UNLEASH_METRICS_INTERVAL: '${UNLEASH_METRICS_INTERVAL:-}'
UNLEASH_API_KEY: '${UNLEASH_API_KEY:-}'
PM2_MACHINE_NAME: '${PM2_MACHINE_NAME:-}'
PM2_SECRET_KEY: '${PM2_SECRET_KEY:-}'
PM2_PUBLIC_KEY: '${PM2_PUBLIC_KEY:-}'
JITSU_SERVER_URL: '${JITSU_SERVER_URL:-}'
JITSU_SERVER_WRITE_KEY: '${JITSU_SERVER_WRITE_KEY:-}'
OTEL_ENABLED: '${OTEL_ENABLED:-}'
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}'
OTEL_EXPORTER_OTLP_HEADERS: '${OTEL_EXPORTER_OTLP_HEADERS:-}'
GAUZY_GITHUB_CLIENT_ID: '${GAUZY_GITHUB_CLIENT_ID:-}'
GAUZY_GITHUB_CLIENT_SECRET: '${GAUZY_GITHUB_CLIENT_SECRET:-}'
GAUZY_GITHUB_APP_PRIVATE_KEY: '${GAUZY_GITHUB_APP_PRIVATE_KEY:-}'
GAUZY_GITHUB_WEBHOOK_URL: '${GAUZY_GITHUB_WEBHOOK_URL:-}'
GAUZY_GITHUB_WEBHOOK_SECRET: '${GAUZY_GITHUB_WEBHOOK_SECRET:-}'
GAUZY_GITHUB_APP_NAME: '${GAUZY_GITHUB_APP_NAME:-}'
GAUZY_GITHUB_REDIRECT_URL: '${GAUZY_GITHUB_REDIRECT_URL:-}'
GAUZY_GITHUB_POST_INSTALL_URL: '${GAUZY_GITHUB_POST_INSTALL_URL:-}'
GAUZY_GITHUB_APP_ID: '${GAUZY_GITHUB_APP_ID:-}'
GAUZY_GITHUB_OAUTH_CLIENT_ID: '${GAUZY_GITHUB_OAUTH_CLIENT_ID:-}'
GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${GAUZY_GITHUB_OAUTH_CLIENT_SECRET:-}'
GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${GAUZY_GITHUB_OAUTH_CALLBACK_URL:-}'
JITSU_BROWSER_URL: '${JITSU_BROWSER_URL:-}'
JITSU_BROWSER_WRITE_KEY: '${JITSU_BROWSER_WRITE_KEY:-}'
MAGIC_CODE_EXPIRATION_TIME: '${MAGIC_CODE_EXPIRATION_TIME:-}'
APP_NAME: '${APP_NAME:-}'
APP_LOGO: '${APP_LOGO:-}'
APP_SIGNATURE: '${APP_SIGNATURE:-}'
APP_LINK: '${APP_LINK:-}'
APP_EMAIL_CONFIRMATION_URL: '${APP_EMAIL_CONFIRMATION_URL:-}'
APP_MAGIC_SIGN_URL: '${APP_MAGIC_SIGN_URL:-}'
COMPANY_LINK: '${COMPANY_LINK:-}'
COMPANY_NAME: '${COMPANY_NAME:-}'
entrypoint: './entrypoint.prod.sh'
command: ['node', 'main.js']
restart: on-failure
ports:
- '3000'
# '3000-3001:'${API_PORT:-3000}''
networks:
- overlay
volumes:
certificates: {}
networks:
overlay:
driver: bridge
within the same demo
directory create another docker-compose file for the nginx proxy docker-compose.api.demo.letsencrypt.pre.yml
version: '3.8'
services:
proxy:
image: jonasal/nginx-certbot:latest
restart: always
environment:
CERTBOT_EMAIL: "[email protected]"
env_file:
- ./nginx-certbot.env
ports:
- "80:80"
- "443:443"
networks:
- demo_overlay
volumes:
- nginx_secrets:/etc/letsencrypt
- ./user_conf.d:/etc/nginx/user_conf.d
volumes:
nginx_secrets: {}
certificates: {}
networks:
demo_overlay:
external: true
Then create the nginx config directory called user_conf.d
and within the nginx config directory create nginx.conf
file user_conf.d/nginx.conf
server {
listen 443 ssl;
server_name apidemodts.gauzy.co;
ssl_certificate /etc/letsencrypt/live/apidemodts.gauzy.co/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/apidemodts.gauzy.co/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/apidemodts.gauzy.co/chain.pem;
# Load the Diffie-Hellman parameter.
ssl_dhparam /etc/letsencrypt/dhparams/dhparam.pem;
location / {
proxy_pass http://api:3000;
}
}
NB: the server_name must match the domain which you want to create the self signed certificate for.
Create a file called deploy-do-droplet-demo.yml
in the .github/workflows
directory
name: Deploy to DigitalOcean Droplet Demo
on:
workflow_run:
workflows: ['Deploy to DigitalOcean Droplet Demo Pre']
branches: [develop]
types:
- completed
jobs:
deploy:
runs-on: buildjet-4vcpu-ubuntu-2204
environment: demo
steps:
- name: checkout out code
uses: actions/checkout@v4
- name: Modify API_BASE_URL
run: |
echo "INGRESS_CERT_TYPE=${{ env.INGRESS_CERT_TYPE }}" >> $GITHUB_ENV
if [ "${{ env.INGRESS_CERT_TYPE }}" = "cloudflare" ]; then
echo "API_BASE_URL=https://apidemodt.gauzy.co" >> $GITHUB_ENV
elif [ "${{ env.INGRESS_CERT_TYPE }}" = "letsencrypt" ]; then
echo "API_BASE_URL=https://apidemodts.gauzy.co" >> $GITHUB_ENV
else
echo "UNKNOWN INGRESS_CERT_TYPE"
fi
env:
INGRESS_CERT_TYPE: 'letsencrypt'
- name: Inject secrets into .env-template.compose
run: |
if [ "${{ env.INGRESS_CERT_TYPE }}" = "cloudflare" ]; then
envsubst < $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.template.yml > temp.yaml && mv temp.yaml $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml
touch $GITHUB_WORKSPACE/.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml
elif [ "${{ env.INGRESS_CERT_TYPE }}" = "letsencrypt" ]; then
envsubst < $GITHUB_WORKSPACE/.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.template.yml > temp.yaml && mv temp.yaml $GITHUB_WORKSPACE/.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml
touch $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml
else
echo "UNKNOWN INGRESS_CERT_TYPE"
fi
env:
ENV_NAME: 'demo'
DEMO: 'true'
NODE_ENV: 'development'
ADMIN_PASSWORD_RESET: 'true'
API_HOST: $API_HOST
API_BASE_URL: '${{ env.API_BASE_URL }}'
CLIENT_BASE_URL: 'https://demo.gauzy.co'
DB_TYPE: '${{ secrets.DB_TYPE }}'
DB_URI: '${{ secrets.DB_URI }}'
DB_HOST: '${{ secrets.DB_HOST }}'
DB_USER: '${{ secrets.DB_USER }}'
DB_PASS: '${{ secrets.DB_PASS }}'
DB_NAME: '${{ secrets.DB_NAME }}'
DB_PORT: '${{ secrets.DB_PORT }}'
DB_CA_CERT: '${{ secrets.DB_CA_CERT }}'
DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}'
DB_POOL_SIZE: '${{ secrets.DB_POOL_SIZE }}'
DB_POOL_SIZE_KNEX: '${{ secrets.DB_POOL_SIZE_KNEX }}'
REDIS_ENABLED: '${{ secrets.REDIS_ENABLED }}'
REDIS_URL: '${{ secrets.REDIS_URL }}'
CLOUD_PROVIDER: 'DO'
SENTRY_DSN: '${{ secrets.SENTRY_DSN }}'
SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}'
SENTRY_PROFILE_SAMPLE_RATE: '${{ secrets.SENTRY_PROFILE_SAMPLE_RATE }}'
SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}'
SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}'
SENTRY_PROFILING_ENABLED: '${{ secrets.SENTRY_PROFILING_ENABLED }}'
AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'
AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}'
AWS_REGION: '${{ secrets.AWS_REGION }}'
AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}'
WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}'
WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}'
WASABI_REGION: '${{ secrets.WASABI_REGION }}'
WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}'
WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}'
EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}'
JWT_SECRET: '${{ secrets.JWT_SECRET }}'
JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}'
JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}'
CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}'
CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}'
CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}'
MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}'
MAIL_HOST: '${{ secrets.MAIL_HOST }}'
MAIL_PORT: '${{ secrets.MAIL_PORT }}'
MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}'
MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}'
ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}'
GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}'
GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}'
GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}'
FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}'
FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}'
FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}'
FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}'
INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}'
UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}'
FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}'
GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}'
GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}'
UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}'
UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}'
UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}'
UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}'
UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}'
UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}'
PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}'
PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}'
PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}'
JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}'
JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}'
OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}'
OTEL_PROVIDER: '${{ secrets.OTEL_PROVIDER }}'
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}'
OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}'
GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}'
GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}'
GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}'
GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}'
GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}'
GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}'
GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}'
GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}'
GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}'
GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}'
GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}'
GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}'
JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}'
JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}'
MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}'
APP_NAME: '${{ secrets.APP_NAME }}'
APP_LOGO: '${{ secrets.APP_LOGO }}'
APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}'
APP_LINK: '${{ secrets.APP_LINK }}'
APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}'
APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}'
COMPANY_LINK: '${{ secrets.COMPANY_LINK }}'
COMPANY_NAME: '${{ secrets.COMPANY_NAME }}'
- name: Copy file via scp - with-cloudflare
if: ${{ env.INGRESS_CERT_TYPE == 'cloudflare' }}
uses: appleboy/scp-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
source: '.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml'
target: '.'
- name: Copy file via scp - with-letsencrypt
if: ${{ env.INGRESS_CERT_TYPE == 'letsencrypt' }}
uses: appleboy/scp-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
source: '.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml'
target: '.'
- name: Deploy to DigitalOcean Droplet
uses: appleboy/ssh-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
envs: INGRESS_CERT_TYPE
script: |
if [ "${{ env.INGRESS_CERT_TYPE }}" = "cloudflare" ]; then
docker-compose -f .deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml down
docker-compose -f .deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml up -d
elif [ "${{ env.INGRESS_CERT_TYPE }}" = "letsencrypt" ]; then
docker-compose -f .deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.yml down
docker-compose -f .deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.yml up -d
else
echo "Unknown INGRESS_CERT_TYPE: $INGRESS_CERT_TYPE"
exit 1
fi
Create a file called deploy-do-droplet-pre-demo.yml
in the .github/workflows
directory
name: Deploy to DigitalOcean Droplet Demo Pre
on:
push:
branches:
- develop
jobs:
deploy:
runs-on: buildjet-4vcpu-ubuntu-2204
environment: demo
steps:
- name: checkout out code
uses: actions/checkout@v4
- name: Globalise Ingress certificate type env
run: |
echo "INGRESS_CERT_TYPE=${{ env.INGRESS_CERT_TYPE }}" >> $GITHUB_ENV
env:
INGRESS_CERT_TYPE: 'letsencrypt'
- name: Generate TLS Secrets for DO Droplet
run: |
rm -f $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/ingress.api.crt $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/ingress.api.key
echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/ingress.api.crt
echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > $GITHUB_WORKSPACE/.deploy/ssh/with-cloudflare/demo/ingress.api.key
- name: Copy file via scp - cloudflare
if: ${{ env.INGRESS_CERT_TYPE == 'cloudflare' }}
uses: appleboy/scp-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
source: '.deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.cloudflare.pre.yml,.deploy/ssh/with-cloudflare/demo/nginx.demo.pre.cloudflare.conf,.deploy/ssh/with-cloudflare/demo/ingress.api.crt,.deploy/ssh/with-cloudflare/demo/ingress.api.key'
target: '.'
- name: Copy file via scp - letsencrypt
if: ${{ env.INGRESS_CERT_TYPE == 'letsencrypt' }}
uses: appleboy/scp-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
source: '.deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.letsencrypt.pre.yml,.deploy/ssh/with-letsencrypt/demo/user_conf.d,.deploy/ssh/with-letsencrypt/demo/nginx-certbot.env'
target: '.'
- name: Install Docker
uses: appleboy/ssh-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
script: |
if ! command -v docker &> /dev/null; then
echo "Docker not installed. Installing..."
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
else
echo "Docker is already installed."
fi
- name: Install Docker Compose
uses: appleboy/ssh-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
script: |
if ! command -v docker-compose &> /dev/null; then
echo "Docker Compose not installed. Installing..."
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
else
echo "Docker Compose is already installed."
fi
- name: Deploy to DigitalOcean Droplet
uses: appleboy/ssh-action@master
with:
host: ${{secrets.DO_DROPLET_DEMO_HOST}}
username: ${{secrets.DO_DROPLET_USERNAME}}
key: ${{secrets.DO_DROPLET_KEY}}
script: |
if [ "${{ env.INGRESS_CERT_TYPE }}" = "cloudflare" ]; then
docker-compose -f .deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.letsencrypt.pre.yml down
docker-compose -f .deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.cloudflare.pre.yml up -d
elif [ "${{ env.INGRESS_CERT_TYPE }}" = "letsencrypt" ]; then
docker-compose -f .deploy/ssh/with-cloudflare/demo/docker-compose.api.demo.cloudflare.pre.yml down
docker-compose -f .deploy/ssh/with-letsencrypt/demo/docker-compose.api.demo.letsencrypt.pre.yml up -d
else
echo "Unknown INGRESS_CERT_TYPE: $INGRESS_CERT_TYPE"
exit 1
fi