Use this guide if you want to run rtSurvey on your own server (VPS, bare metal, or local machine) using the public Docker image rtawebteam/rtcloud:survey-public.
No Docker Hub login required — the image is public.
The container bundles everything in a single image:
- Apache + PHP (application server)
- Shiny Server (analytics dashboards, port 3838)
- Beanstalkd (background job queue)
Requirements
Server
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 4 GB | 8 GB |
| Disk | 50 GB | 100 GB SSD |
| CPU | 2 vCPUs | 4 vCPUs |
| OS | Ubuntu 22.04 LTS | Ubuntu 22.04 LTS |
Software
- Docker Engine 24.0+ — install guide (opens in a new tab)
- Docker Compose v2.0+ — included with Docker Desktop; on Linux:
apt install docker-compose-plugin
Setup
Create a working directory
mkdir -p /opt/rtsurvey && cd /opt/rtsurveyCreate docker-compose.yml
services:
mysql:
image: mysql:8.0
container_name: rtsurvey-mysql
restart: unless-stopped
command: >
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8
--collation-server=utf8_unicode_ci
--sql-mode=NO_ENGINE_SUBSTITUTION
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE:-smartsurvey}
MYSQL_USER: ${MYSQL_USER:-smartsurvey}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_HOST: '%'
volumes:
- mysql_data:/var/lib/mysql
networks:
- rtsurvey-net
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
rtsurvey:
image: rtawebteam/rtcloud:survey-public
container_name: rtsurvey-app
restart: unless-stopped
entrypoint: ["/bin/entrypoint-production.sh"]
depends_on:
mysql:
condition: service_healthy
ports:
- "${APP_PORT:-8080}:80"
- "${SHINY_PORT:-3838}:3838"
env_file:
- .env
volumes:
- app_uploads:/var/www/html/smartsurvey/uploads
- app_audios:/var/www/html/smartsurvey/audios
- app_downloads:/var/www/html/smartsurvey/downloads
- app_gallery:/var/www/html/smartsurvey/gallery
- app_tmp:/var/www/html/smartsurvey/tmp
- app_cache:/var/www/html/smartsurvey/cache
- app_runtime:/var/www/html/smartsurvey/protected/runtime
- app_assets:/var/www/html/smartsurvey/assets
- app_aggregate:/var/www/html/smartsurvey/aggregate
networks:
- rtsurvey-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
# Optional: Embedded Keycloak SSO
# Start with: docker compose --profile embed-keycloak up -d
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: rtsurvey-keycloak
restart: unless-stopped
profiles:
- embed-keycloak
command: start --import-realm
environment:
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN_USER:-admin}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_HTTP_ENABLED: "true"
KC_HTTP_RELATIVE_PATH: /auth
KC_PROXY_HEADERS: xforwarded
KC_HOSTNAME: ${KC_HOSTNAME}
KC_HOSTNAME_STRICT: "false"
KC_DB: mysql
KC_DB_URL_DATABASE: ${KEYCLOAK_DB:-keycloak}
KC_DB_URL_HOST: mysql
KC_DB_USERNAME: ${KEYCLOAK_DB_USER:-keycloak}
KC_DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
volumes:
- ./keycloak-import:/opt/keycloak/data/import
ports:
- "${KEYCLOAK_PORT:-8091}:8080"
depends_on:
mysql:
condition: service_healthy
networks:
- rtsurvey-net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9000/health/ready || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 120s
volumes:
mysql_data:
app_uploads:
app_audios:
app_downloads:
app_gallery:
app_tmp:
app_cache:
app_runtime:
app_assets:
app_aggregate:
networks:
rtsurvey-net:
driver: bridgeCreate .env
# Project
PROJECT_ID=mysurvey
PROJECT_TYPE=rtsurvey
PROJECT_URL=your-server-ip-or-domain
HTTP_PROTOCOL=http
TZ=UTC
# Database
MYSQL_HOST=mysql
MYSQL_DATABASE=smartsurvey
MYSQL_USER=smartsurvey
MYSQL_ROOT_PASSWORD=change-this-root-password
MYSQL_PASSWORD=change-this-app-password
# Admin (set before first startup — cannot change via env after DB is created)
ADMIN_PASSWORD=change-this-admin-password
# Runtime
RUN_ENV=prod
RUN_MODE=admin
GII_ENABLED=false
CSRF_VALIDATION_ENABLED=true
# Ports (optional — defaults shown)
APP_PORT=8080
SHINY_PORT=3838Change MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD before starting. Use strong random strings (32+ characters) in production.
Start the stack
docker compose up -dThe first startup takes about 60–90 seconds — MySQL initializes the database on the first run.
Verify
# Check all containers are running
docker compose ps
# Check application health
curl http://localhost:8080/healthFirst Login
Once the health check passes, open your browser:
http://your-server:8080/cpms/cpmsSite/login| Field | Value |
|---|---|
| Username | admin |
| Password | value of ADMIN_PASSWORD in your .env (defaults to admin) |
Set ADMIN_PASSWORD in your .env before first startup. The entrypoint sets it on first run — changing it after the database is initialized has no effect.
Environment Variables
Required by the app container
The entrypoint will refuse to start if any of these are missing:
| Variable | Description |
|---|---|
PROJECT_ID | Unique identifier for this instance (alphanumeric, no spaces) |
PROJECT_URL | Domain or IP where the app is accessed (no http:// prefix) |
MYSQL_HOST | MySQL hostname — use the Docker service name (e.g. mysql) |
MYSQL_DATABASE | Database name |
MYSQL_PASSWORD | Database user password |
Required by the MySQL container
| Variable | Description |
|---|---|
MYSQL_ROOT_PASSWORD | MySQL root password (used by the mysql container, not the app) |
Recommended
| Variable | Default | Description |
|---|---|---|
MYSQL_USER | smartsurvey | Database username |
ADMIN_PASSWORD | admin | Initial admin password — set before first startup, cannot change via env after DB is created |
HTTP_PROTOCOL | https | Set to http if not using SSL |
PROJECT_PORT | 80 | External port users connect to (not the internal Docker port) |
PROJECT_TYPE | rtwork | Platform type — use rtsurvey for the survey platform |
TZ | UTC | Timezone (e.g. Asia/Bangkok, America/New_York) |
Runtime
| Variable | Default | Description |
|---|---|---|
RUN_ENV | prod | Environment mode (prod or dev) |
RUN_MODE | admin | Service role — use admin for a single-server setup |
GII_ENABLED | false | Keep false in production (Yii code generator) |
CSRF_VALIDATION_ENABLED | true | CSRF protection — keep true |
License
| Variable | Default | Description |
|---|---|---|
REQUIRE_LICENSE | false | Set to true to enforce license validation |
RTCLOUD_LICENSE_KEY | — | Your license key — required when REQUIRE_LICENSE=true |
Data Persistence
All user data is stored in named Docker volumes. These survive container restarts and image updates.
| Volume | Contents |
|---|---|
mysql_data | Database (surveys, users, responses) |
app_uploads | File uploads from surveys |
app_gallery | Media library |
app_downloads | Generated export files |
app_audios | Audio recordings |
app_runtime | PHP session and cache files |
app_aggregate | Aggregated analytics data |
Common Operations
View logs
docker compose logs -f rtsurvey
docker compose logs -f mysqlRestart the app
docker compose restart rtsurveyAccess a shell
docker compose exec rtsurvey bashRun database migrations manually
docker compose exec rtsurvey php /var/www/html/smartsurvey/protected/yiic migrateUpgrades
The image is tagged survey-public. To update to the latest version:
docker compose pull
docker compose up -dDatabase migrations run automatically on startup via the production entrypoint.
Backup & Restore
Backup database
docker compose exec mysql \
mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" smartsurvey \
> backup-$(date +%Y%m%d).sqlRestore database
docker compose exec -T mysql \
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" smartsurvey \
< backup-20240101.sqlBackup uploaded files
docker run --rm \
-v rtsurvey_app_uploads:/data \
-v $(pwd):/backup \
alpine tar czf /backup/uploads-$(date +%Y%m%d).tar.gz -C /data .Embedded Keycloak (Optional SSO)
The production compose file includes an optional Keycloak container for SSO. It shares the same MySQL instance and is activated via a Docker Compose profile.
Embedded Keycloak requires at least 4 GB RAM. It is optional — skip this if you don't need SSO.
Add Keycloak vars to .env
# Keycloak admin
KEYCLOAK_ADMIN_USER=admin
KEYCLOAK_ADMIN_PASSWORD=change-this-keycloak-password
# Keycloak database (created automatically in the shared MySQL on first run)
KEYCLOAK_DB=keycloak
KEYCLOAK_DB_USER=keycloak
KEYCLOAK_DB_PASSWORD=change-this-keycloak-db-password
# KC_HOSTNAME: full public URL of your server (Keycloak appends /auth automatically)
KC_HOSTNAME=http://your-server:8091
# Port Keycloak listens on (host)
KEYCLOAK_PORT=8091
# Tell the rtCloud app to connect to embedded Keycloak
EMBED_KEYCLOAK=true
KEYCLOAK_URL=http://your-server:8091
KEYCLOAK_REALM=rtsurvey
KEYCLOAK_CLIENT_ID=rtsurvey
KEYCLOAK_CLIENT_SECRET=your-client-secretThe keycloak service is already included in the docker-compose.yml above (under the embed-keycloak profile — it won't start unless you use --profile embed-keycloak).
Create the import directory (Keycloak will start without it but realm auto-provisioning won't work):
mkdir -p /opt/rtsurvey/keycloak-importStart with Keycloak
docker compose --profile embed-keycloak up -dTo start without Keycloak (default):
docker compose up -dKeycloak admin console
Once healthy, access the Keycloak admin UI at:
http://your-server:8091/auth/adminLogin with KEYCLOAK_ADMIN_USER / KEYCLOAK_ADMIN_PASSWORD.
Next Steps
- HTTPS — Put nginx or Caddy in front and follow the SSL Setup guide
- SSO — Connect Azure AD or Keycloak via the SSO Authentication guide
- Cloud deployment — See Cloud Providers for provider-specific setup with automated scripts