This is a continuation blog post of the implementation, and also includes a section on testing. The past blog post detailed provisioning a cloud virtual machine, installing and configuring Docker, implementing swap memory, designating a static IP, and a public DNS. This blog post includes configuring Formbricks and Traefiks, as well as deployment and testing.
Formbricks Setup
I downloaded the Docker Compose file plus the Cube configuration with the given script:
mkdir -p cube/schema
curl -o docker-compose.yml https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/docker-compose.yml
curl -o cube/cube.js https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/cube/cube.js
curl -o cube/schema/FeedbackRecords.js https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/cube/schema/FeedbackRecords.js
New to v5 (released may 25 2026), Formbricks also bundles Cube, which is an analytics engine. Both Formbricks and cube require shared secrets, which will be stored in an environment variable. These are randomly generated using openssl, into random 32 byte hex values. The scripts are provided too:
cat <<EOF > .env
HUB_API_KEY=$(openssl rand -hex 32)
CUBEJS_API_SECRET=$(openssl rand -hex 32)
CUBEJS_JWT_ISSUER=formbricks-web
CUBEJS_JWT_AUDIENCE=formbricks-cube
EOF
Next we setup the NextAuth secret, cron secret, HUB API key, and encryption keys using openssl as well.
sed -i “/NEXTAUTH_SECRET:$/s/NEXTAUTH_SECRET:.*/NEXTAUTH_SECRET: $(openssl rand -hex 32)/” docker-compose.yml
sed -i “/ENCRYPTION_KEY:$/s/ENCRYPTION_KEY:.*/ENCRYPTION_KEY: $(openssl rand -hex 32)/” docker-compose.yml
sed -i “/CRON_SECRET:$/s/CRON_SECRET:.*/CRON_SECRET: $(openssl rand -hex 32)/” docker-compose.yml
sed -i “/HUB_API_KEY:$/s/HUB_API_KEY:.*/HUB_API_KEY: $(openssl rand -hex 32)/” docker-compose.yml
This next step ensures that Formbricks knows what it’s public-facing address is, for routing, authentication and sharing links. Here we edit WEBAPP_URL, and NEXTAUTH_URL, and the script is also provided:
sed -i “s|^ WEBAPP_URL:.*| WEBAPP_URL: https://secure-form.duckdns.org|” docker-compose.yml
sed -i “s|^ NEXTAUTH_URL:.*| NEXTAUTH_URL: https://secure-form.duckdns.org|” docker-compose.yml
At this point Formbricks is setup. We could spin it up using the command ‘docker compose up -d’, but it would be over unencrypted HTTP. Since this is a secure form that handles sensitive personal information, we need to secure data in transit. The next step is to put a reverse proxy in front.
Setting up the Traefik reverse proxy
Formbricks provides an official one-click installation script, formbricks.sh [12], which handles the process such as installing Docker, generating secrets, and creating the Traefik configuration. Running that would have been faster, but I wanted to fully understand the configuration process. I wanted to learn how to integrate the reverse proxy, so instead of running the script I inspected its source on GitHub, and then added the configuration manually via scripts. The result is the same secure setup, but one I understand more deeply.
The Traefik documentation details two types of configuration files, static and dynamic configuration [14]. The script creates a static configuration file at traefik.yaml, and a dynamic configuration file at traefik-dynamic.yaml.
cat > traefik.yaml << EOF
entryPoints:
web:
address: “:80”
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: “:443”
http:
tls:
certResolver: default
options: default
providers:
docker:
watch: true
exposedByDefault: false
certificatesResolvers:
default:
acme:
email: $email_address
storage: acme.json
caServer: “https://acme-v02.api.letsencrypt.org/directory”
tlsChallenge: {}
EOF
The next file that needs to be created, traefik-dynamic.yaml, specifies the hardening strategy for handling TLS connections, such as requiring a minimum TLS version of 1.2 when accepting TLS requests.
cat <<EOT >traefik-dynamic.yaml
# configuring min TLS version
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
# TLS 1.2 strong ciphers
– TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
– TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
– TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
– TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
– TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
– TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
– TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
– TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
# TLS 1.3 ciphers are not configurable in Traefik; they are enabled by default
curvePreferences:
– CurveP521
– CurveP384
sniStrict: true
alpnProtocols:
– h2
– http/1.1
– acme-tls/1
EOT
The Traefik reverse proxy is then added to the existing docker-compose.yml, alongside the Formbricks application and other dependencies. Formbricks’ documentation also shows how this Traefik configuration should look like [13]. This is added manually without any scripts, and this section is added inside the “services:” section.
traefik:
image: “traefik:v3.6.4”
restart: always
container_name: “traefik”
depends_on:
– formbricks
ports:
– “80:80”
– “443:443”
volumes:
– ./traefik.yaml:/traefik.yaml
– ./traefik-dynamic.yaml:/traefik-dynamic.yaml
– ./acme.json:/acme.json
– /var/run/docker.sock:/var/run/docker.sock:ro
Next we need a place to store the SSL certificate, which will be stored in an acme.json file.
touch acme.json
chmod 600 acme.json
Next we need to add the routing labels to the Formbricks service in the same docker-compose.yml file earlier, under “services:”. These are also manually added.
labels:
– “traefik.enable=true”
– “traefik.http.routers.formbricks.rule=Host(`secure-form.duckdns.org`)”
– “traefik.http.routers.formbricks.entrypoints=websecure”
– “traefik.http.routers.formbricks.tls=true”
– “traefik.http.routers.formbricks.tls.certresolver=default”
– “traefik.http.services.formbricks.loadbalancer.server.port=3000”
Now the next steps is to validate and launch, by running the script below:
docker compose config >/dev/null && echo OK
docker compose up -d
At this point everything was running without errors, and the next step was to test this setup.
Testing
The first check is to confirm which application exposes its ports to the public, and which application is internal, and not reachable via the internet.
docker compose ps
Output:
formbricks-cube-1 cubejs/cube:v1.6.6 “docker-entrypoint.s…” cube 2 days ago Up 24 minutes (healthy) 4000/tcp
formbricks-formbricks-1 ghcr.io/formbricks/formbricks:latest “docker-entrypoint.s…” formbricks 2 days ago Up 24 minutes 3000/tcp
formbricks-hub-1 ghcr.io/formbricks/hub:latest “/app/hub-api” hub 2 days ago Up 24 minutes (healthy) 8080/tcp
formbricks-postgres-1 pgvector/pgvector:pg18 “docker-entrypoint.s…” postgres 2 days ago Up 24 minutes (healthy) 5432/tcp
formbricks-redis-1 valkey/valkey@sha256:12ba4f45a7c3e1d0f076acd616cb230834e75a77e8516dde382720af32832d6d “docker-entrypoint.s…” redis 2 days ago Up 24 minutes (healthy) 6379/tcp
formbricks-traefik-1 traefik:v3.7 “/entrypoint.sh –pr…” traefik 2 days ago Up 24 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp
This confirmed that only the Traefik service is reachable from the internet, via ports 80 and 443. The next step is to verify that HTTP requests are redirected to HTTPS, so that data in transit remains encrypted.
Verifying the HTTP to HTTPS redirect:
curl -sI http://secure-form.duckdns.org
Output shown as “HTTP/1.1 308 Permanent Redirect”, which is the preferred response. The server is responding by saying to use the HTTPS connection instead.
Verifying HTTPS works:
curl -sI https://secure-form.duckdns.org
Output shown as HTTP/2 200, meaning a successful connection to the server over HTTPS.
Verifying the internal ports not reachable via internet:
curl -m 5 http://34.59.150.138:3000
curl -m 5 http://34.59.150.138:6379
curl -m 5 http://34.59.150.138:5432
Output shown as connection timeout for all 3 ports, targeting the VM’s public ip address directly.
Verifying the TLS hardening scheme:
curl -I –tlsv1.0 –tls-max 1.0 https://secure-form.duckdns.org
Output shown as an error, meaning the minimum TLS version of 1.2 that’s accepted is being enforced.
References
[1] Docker, “Install Docker Engine on Debian,” Docker Inc. [Online]. Available: https://docs.docker.com/engine/install/debian/. [Accessed: Jun. 6, 2026].
[2] Google Cloud, “Create and start a Compute Engine instance,” Google. [Online]. Available: https://docs.cloud.google.com/compute/docs/instances/create-start-instance. [Accessed: Jun. 6, 2026].
[3] Formbricks, “Self-hosting overview,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/overview. [Accessed: Jun. 10, 2026].
[4] L. Tesar, “Create a Linux swap file,” Linuxize. [Online]. Available: https://linuxize.com/post/create-a-linux-swap-file/. [Accessed: Jun. 11, 2026].
[5] Formbricks, “Docker setup,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/setup/docker. [Accessed: Jun. 13, 2026].
[6] Formbricks, “Domain configuration,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/configuration/domain-configuration. [Accessed: Jun. 18, 2026].
[7] GNU, “sed, a stream editor,” Free Software Foundation. [Online]. Available: https://www.gnu.org/software/sed/manual/sed.html. [Accessed: Jun. 18, 2026].
[8] Docker, “Use Traefik as a reverse proxy,” Docker Inc. [Online]. Available: https://docs.docker.com/guides/traefik/. [Accessed: Jun. 18, 2026].
[9] Traefik Labs, “Getting started with Docker and Traefik,” Traefik Labs. [Online]. Available: https://doc.traefik.io/traefik/getting-started/quick-start-with-docker/. [Accessed: Jun. 18, 2026].
[10] Traefik Labs, “Docker-compose with Let’s Encrypt: TLS challenge,” Traefik Labs. [Online]. Available: https://doc.traefik.io/traefik/v3.3/user-guides/docker-compose/acme-tls/. [Accessed: Jun. 18, 2026].
[11] Docker, “Port publishing and mapping,” Docker Inc. [Online]. Available: https://docs.docker.com/engine/network/port-publishing/. [Accessed: Jun. 18, 2026].
[12] Formbricks, “formbricks.sh,” GitHub. [Online]. Available: https://github.com/formbricks/formbricks/blob/main/docker/formbricks.sh. [Accessed: Jun. 18, 2026].
[13] Formbricks, “Custom SSL Certificate,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/configuration/custom-ssl. [Accessed: Jun. 18, 2026].
[14] Traefik Labs, “Configuration Overview,” Traefik Labs. [Online]. Available: https://doc.traefik.io/traefik/getting-started/configuration-overview/. [Accessed: Jun. 18, 2026].