Dokploy

Struxa

Struxa is a self-hosted, open-source server management panel — a modern, fully typed replacement for Pterodactyl.

Struxa logo

Configuration

version: "3.8"
services:
  mysql:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    volumes:
      - struxa-mysql:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 5s
      timeout: 5s
      retries: 30
      start_period: 60s

  minio:
    image: minio/minio:RELEASE.2025-04-22T22-12-26Z
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      - MINIO_ROOT_USER=${MINIO_ACCESS_KEY}
      - MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY}
    volumes:
      - struxa-minio:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 5s
      retries: 10

  migrate:
    image: ghcr.io/struxadotcloud/migrate:${IMAGE_TAG}
    restart: on-failure:5
    environment:
      - DATABASE_URL=${DATABASE_URL}
    depends_on:
      mysql:
        condition: service_healthy

  struxa:
    image: ghcr.io/struxadotcloud/dashboard:${IMAGE_TAG}
    restart: unless-stopped
    entrypoint:
      - /bin/sh
      - -c
      - |
        eval "$$(node -e '
          const {generateKeyPairSync}=require("crypto");
          const {publicKey,privateKey}=generateKeyPairSync("rsa",{modulusLength:2048,
            publicKeyEncoding:{type:"spki",format:"pem"},
            privateKeyEncoding:{type:"pkcs8",format:"pem"}});
          console.log("export JWT_PRIVATE_KEY="+JSON.stringify(privateKey.replace(/\n/g,"\\n")));
          console.log("export JWT_PUBLIC_KEY="+JSON.stringify(publicKey.replace(/\n/g,"\\n")));
        ')"
        exec node apps/web/server.js
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
      - BETTER_AUTH_URL=${BETTER_AUTH_URL}
      - CORS_ORIGIN=${CORS_ORIGIN}
      - APP_URL=${APP_URL}
      - DATABASE_ENCRYPTION_KEY=${DATABASE_ENCRYPTION_KEY}
      - MINIO_ENDPOINT=${MINIO_ENDPOINT}
      - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
      - MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
      - MINIO_BUCKET=${MINIO_BUCKET}
      - NODE_ENV=production
    depends_on:
      migrate:
        condition: service_completed_successfully
      minio:
        condition: service_healthy

  watchkeeper:
    image: ghcr.io/struxadotcloud/watchkeeper:${IMAGE_TAG}
    restart: unless-stopped
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - DATABASE_ENCRYPTION_KEY=${DATABASE_ENCRYPTION_KEY}
    depends_on:
      migrate:
        condition: service_completed_successfully

volumes:
  struxa-mysql:
  struxa-minio:
[variables]
main_domain = "${domain}"
image_tag = "latest"
mysql_root_password = "${password:32}"
mysql_password = "${password:32}"
better_auth_secret = "${base64:32}"
db_encryption_key = "${hash:64}"
minio_access_key = "${username}"
minio_secret_key = "${password:32}"

[config]
env = [
  "IMAGE_TAG=${image_tag}",
  "MYSQL_ROOT_PASSWORD=${mysql_root_password}",
  "MYSQL_DATABASE=struxa",
  "MYSQL_USER=struxa",
  "MYSQL_PASSWORD=${mysql_password}",
  "DATABASE_URL=mysql://struxa:${mysql_password}@mysql:3306/struxa",
  "BETTER_AUTH_SECRET=${better_auth_secret}",
  "BETTER_AUTH_URL=http://${main_domain}",
  "APP_URL=http://${main_domain}",
  "CORS_ORIGIN=http://${main_domain}",
  "DATABASE_ENCRYPTION_KEY=${db_encryption_key}",
  "MINIO_ENDPOINT=http://minio:9000",
  "MINIO_ACCESS_KEY=${minio_access_key}",
  "MINIO_SECRET_KEY=${minio_secret_key}",
  "MINIO_BUCKET=struxa",
]
mounts = []

[[config.domains]]
serviceName = "struxa"
port = 3001
host = "${main_domain}"

Base64

To import this template in Dokploy: create a Compose service → AdvancedBase64 import and paste the content below:

ewogICJjb21wb3NlIjogInZlcnNpb246IFwiMy44XCJcbnNlcnZpY2VzOlxuICBteXNxbDpcbiAgICBpbWFnZTogbXlzcWw6OC40XG4gICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWRcbiAgICBlbnZpcm9ubWVudDpcbiAgICAgIC0gTVlTUUxfUk9PVF9QQVNTV09SRD0ke01ZU1FMX1JPT1RfUEFTU1dPUkR9XG4gICAgICAtIE1ZU1FMX0RBVEFCQVNFPSR7TVlTUUxfREFUQUJBU0V9XG4gICAgICAtIE1ZU1FMX1VTRVI9JHtNWVNRTF9VU0VSfVxuICAgICAgLSBNWVNRTF9QQVNTV09SRD0ke01ZU1FMX1BBU1NXT1JEfVxuICAgIHZvbHVtZXM6XG4gICAgICAtIHN0cnV4YS1teXNxbDovdmFyL2xpYi9teXNxbFxuICAgIGhlYWx0aGNoZWNrOlxuICAgICAgdGVzdDogW1wiQ01EXCIsIFwibXlzcWxhZG1pblwiLCBcInBpbmdcIiwgXCItaFwiLCBcImxvY2FsaG9zdFwiLCBcIi11XCIsIFwicm9vdFwiLCBcIi1wJHtNWVNRTF9ST09UX1BBU1NXT1JEfVwiXVxuICAgICAgaW50ZXJ2YWw6IDVzXG4gICAgICB0aW1lb3V0OiA1c1xuICAgICAgcmV0cmllczogMzBcbiAgICAgIHN0YXJ0X3BlcmlvZDogNjBzXG5cbiAgbWluaW86XG4gICAgaW1hZ2U6IG1pbmlvL21pbmlvOlJFTEVBU0UuMjAyNS0wNC0yMlQyMi0xMi0yNlpcbiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZFxuICAgIGNvbW1hbmQ6IHNlcnZlciAvZGF0YSAtLWNvbnNvbGUtYWRkcmVzcyBcIjo5MDAxXCJcbiAgICBlbnZpcm9ubWVudDpcbiAgICAgIC0gTUlOSU9fUk9PVF9VU0VSPSR7TUlOSU9fQUNDRVNTX0tFWX1cbiAgICAgIC0gTUlOSU9fUk9PVF9QQVNTV09SRD0ke01JTklPX1NFQ1JFVF9LRVl9XG4gICAgdm9sdW1lczpcbiAgICAgIC0gc3RydXhhLW1pbmlvOi9kYXRhXG4gICAgaGVhbHRoY2hlY2s6XG4gICAgICB0ZXN0OiBbXCJDTURcIiwgXCJjdXJsXCIsIFwiLWZcIiwgXCJodHRwOi8vbG9jYWxob3N0OjkwMDAvbWluaW8vaGVhbHRoL2xpdmVcIl1cbiAgICAgIGludGVydmFsOiAxMHNcbiAgICAgIHRpbWVvdXQ6IDVzXG4gICAgICByZXRyaWVzOiAxMFxuXG4gIG1pZ3JhdGU6XG4gICAgaW1hZ2U6IGdoY3IuaW8vc3RydXhhZG90Y2xvdWQvbWlncmF0ZToke0lNQUdFX1RBR31cbiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlOjVcbiAgICBlbnZpcm9ubWVudDpcbiAgICAgIC0gREFUQUJBU0VfVVJMPSR7REFUQUJBU0VfVVJMfVxuICAgIGRlcGVuZHNfb246XG4gICAgICBteXNxbDpcbiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHlcblxuICBzdHJ1eGE6XG4gICAgaW1hZ2U6IGdoY3IuaW8vc3RydXhhZG90Y2xvdWQvZGFzaGJvYXJkOiR7SU1BR0VfVEFHfVxuICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkXG4gICAgZW50cnlwb2ludDpcbiAgICAgIC0gL2Jpbi9zaFxuICAgICAgLSAtY1xuICAgICAgLSB8XG4gICAgICAgIGV2YWwgXCIkJChub2RlIC1lICdcbiAgICAgICAgICBjb25zdCB7Z2VuZXJhdGVLZXlQYWlyU3luY309cmVxdWlyZShcImNyeXB0b1wiKTtcbiAgICAgICAgICBjb25zdCB7cHVibGljS2V5LHByaXZhdGVLZXl9PWdlbmVyYXRlS2V5UGFpclN5bmMoXCJyc2FcIix7bW9kdWx1c0xlbmd0aDoyMDQ4LFxuICAgICAgICAgICAgcHVibGljS2V5RW5jb2Rpbmc6e3R5cGU6XCJzcGtpXCIsZm9ybWF0OlwicGVtXCJ9LFxuICAgICAgICAgICAgcHJpdmF0ZUtleUVuY29kaW5nOnt0eXBlOlwicGtjczhcIixmb3JtYXQ6XCJwZW1cIn19KTtcbiAgICAgICAgICBjb25zb2xlLmxvZyhcImV4cG9ydCBKV1RfUFJJVkFURV9LRVk9XCIrSlNPTi5zdHJpbmdpZnkocHJpdmF0ZUtleS5yZXBsYWNlKC9cXG4vZyxcIlxcXFxuXCIpKSk7XG4gICAgICAgICAgY29uc29sZS5sb2coXCJleHBvcnQgSldUX1BVQkxJQ19LRVk9XCIrSlNPTi5zdHJpbmdpZnkocHVibGljS2V5LnJlcGxhY2UoL1xcbi9nLFwiXFxcXG5cIikpKTtcbiAgICAgICAgJylcIlxuICAgICAgICBleGVjIG5vZGUgYXBwcy93ZWIvc2VydmVyLmpzXG4gICAgZW52aXJvbm1lbnQ6XG4gICAgICAtIERBVEFCQVNFX1VSTD0ke0RBVEFCQVNFX1VSTH1cbiAgICAgIC0gQkVUVEVSX0FVVEhfU0VDUkVUPSR7QkVUVEVSX0FVVEhfU0VDUkVUfVxuICAgICAgLSBCRVRURVJfQVVUSF9VUkw9JHtCRVRURVJfQVVUSF9VUkx9XG4gICAgICAtIENPUlNfT1JJR0lOPSR7Q09SU19PUklHSU59XG4gICAgICAtIEFQUF9VUkw9JHtBUFBfVVJMfVxuICAgICAgLSBEQVRBQkFTRV9FTkNSWVBUSU9OX0tFWT0ke0RBVEFCQVNFX0VOQ1JZUFRJT05fS0VZfVxuICAgICAgLSBNSU5JT19FTkRQT0lOVD0ke01JTklPX0VORFBPSU5UfVxuICAgICAgLSBNSU5JT19BQ0NFU1NfS0VZPSR7TUlOSU9fQUNDRVNTX0tFWX1cbiAgICAgIC0gTUlOSU9fU0VDUkVUX0tFWT0ke01JTklPX1NFQ1JFVF9LRVl9XG4gICAgICAtIE1JTklPX0JVQ0tFVD0ke01JTklPX0JVQ0tFVH1cbiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvblxuICAgIGRlcGVuZHNfb246XG4gICAgICBtaWdyYXRlOlxuICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseVxuICAgICAgbWluaW86XG4gICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5XG5cbiAgd2F0Y2hrZWVwZXI6XG4gICAgaW1hZ2U6IGdoY3IuaW8vc3RydXhhZG90Y2xvdWQvd2F0Y2hrZWVwZXI6JHtJTUFHRV9UQUd9XG4gICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWRcbiAgICBlbnZpcm9ubWVudDpcbiAgICAgIC0gREFUQUJBU0VfVVJMPSR7REFUQUJBU0VfVVJMfVxuICAgICAgLSBEQVRBQkFTRV9FTkNSWVBUSU9OX0tFWT0ke0RBVEFCQVNFX0VOQ1JZUFRJT05fS0VZfVxuICAgIGRlcGVuZHNfb246XG4gICAgICBtaWdyYXRlOlxuICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseVxuXG52b2x1bWVzOlxuICBzdHJ1eGEtbXlzcWw6XG4gIHN0cnV4YS1taW5pbzpcbiIsCiAgImNvbmZpZyI6ICJbdmFyaWFibGVzXVxubWFpbl9kb21haW4gPSBcIiR7ZG9tYWlufVwiXG5pbWFnZV90YWcgPSBcImxhdGVzdFwiXG5teXNxbF9yb290X3Bhc3N3b3JkID0gXCIke3Bhc3N3b3JkOjMyfVwiXG5teXNxbF9wYXNzd29yZCA9IFwiJHtwYXNzd29yZDozMn1cIlxuYmV0dGVyX2F1dGhfc2VjcmV0ID0gXCIke2Jhc2U2NDozMn1cIlxuZGJfZW5jcnlwdGlvbl9rZXkgPSBcIiR7aGFzaDo2NH1cIlxubWluaW9fYWNjZXNzX2tleSA9IFwiJHt1c2VybmFtZX1cIlxubWluaW9fc2VjcmV0X2tleSA9IFwiJHtwYXNzd29yZDozMn1cIlxuXG5bY29uZmlnXVxuZW52ID0gW1xuICBcIklNQUdFX1RBRz0ke2ltYWdlX3RhZ31cIixcbiAgXCJNWVNRTF9ST09UX1BBU1NXT1JEPSR7bXlzcWxfcm9vdF9wYXNzd29yZH1cIixcbiAgXCJNWVNRTF9EQVRBQkFTRT1zdHJ1eGFcIixcbiAgXCJNWVNRTF9VU0VSPXN0cnV4YVwiLFxuICBcIk1ZU1FMX1BBU1NXT1JEPSR7bXlzcWxfcGFzc3dvcmR9XCIsXG4gIFwiREFUQUJBU0VfVVJMPW15c3FsOi8vc3RydXhhOiR7bXlzcWxfcGFzc3dvcmR9QG15c3FsOjMzMDYvc3RydXhhXCIsXG4gIFwiQkVUVEVSX0FVVEhfU0VDUkVUPSR7YmV0dGVyX2F1dGhfc2VjcmV0fVwiLFxuICBcIkJFVFRFUl9BVVRIX1VSTD1odHRwOi8vJHttYWluX2RvbWFpbn1cIixcbiAgXCJBUFBfVVJMPWh0dHA6Ly8ke21haW5fZG9tYWlufVwiLFxuICBcIkNPUlNfT1JJR0lOPWh0dHA6Ly8ke21haW5fZG9tYWlufVwiLFxuICBcIkRBVEFCQVNFX0VOQ1JZUFRJT05fS0VZPSR7ZGJfZW5jcnlwdGlvbl9rZXl9XCIsXG4gIFwiTUlOSU9fRU5EUE9JTlQ9aHR0cDovL21pbmlvOjkwMDBcIixcbiAgXCJNSU5JT19BQ0NFU1NfS0VZPSR7bWluaW9fYWNjZXNzX2tleX1cIixcbiAgXCJNSU5JT19TRUNSRVRfS0VZPSR7bWluaW9fc2VjcmV0X2tleX1cIixcbiAgXCJNSU5JT19CVUNLRVQ9c3RydXhhXCIsXG5dXG5tb3VudHMgPSBbXVxuXG5bW2NvbmZpZy5kb21haW5zXV1cbnNlcnZpY2VOYW1lID0gXCJzdHJ1eGFcIlxucG9ydCA9IDMwMDFcbmhvc3QgPSBcIiR7bWFpbl9kb21haW59XCJcbiIKfQ==

Tags

hosting, game-server, panel, self-hosted


Version: latest

On this page