Skip to content

Installation

A new installation is set up by the one-click installer, see One-command trial. This page covers switching over on a server that runs calServer v1 today: turning V2 on and importing the data from v1.

Detailed first-time installation

A complete step-by-step guide can be found at First-Time Installation. This page provides an overview of the essential steps.


Activate V2

Set the following variables in the .env file in the project directory (deploy directory, e.g. /var/calserver/.env):

ENABLE_V2=true

# Database engine for V2: pgsql (default), mysql, or sqlsrv
# pgsql starts its own PostgreSQL container and requires PG_PASSWORD;
# mysql keeps using the existing V1 MySQL database.
V2_DB_CONNECTION=pgsql
PG_PASSWORD=<secure-password>

PG_PASSWORD is required with pgsql

Without PG_PASSWORD set, deploy.sh skips the V2 services entirely — no V2 containers will be started.

Decide DMS_STORAGE_MODE now, not later

Where V2 keeps its documents is settled during setup: own (default, its own tree under storage/app — for an installation built alongside a running V1) or v1-tree (V2 keeps writing in the V1 file manager tree — for a plain update of an existing installation). Switching after the first upload leaves the documents already stored behind at the old location.

# own (default) or v1-tree
DMS_STORAGE_MODE=own

deploy.sh derives from this whether the file manager tree is mounted read-only; there is no second switch to set. In detail: Operations → File storage.

Then start the deployment:

./deploy.sh

The script automatically creates and starts all required Docker containers.


To run future updates directly from within the application (in-app updates instead of a manual deploy.sh), activate the updater sidecar. All it takes is a token in the host .env — ideally right before the first deployment:

echo "UPDATER_TOKEN=$(openssl rand -hex 32)" >> .env

deploy.sh then automatically activates the Compose profile v2-updater and starts the calserver-updater container. Update checks, updates, rollbacks, and the version history are then available in the admin interface under Administration → Updates (/admin/updates). The updater recreates the V2 containers only — the V1 installation remains untouched.

For private Docker images, additionally set DOCKERHUB_USERNAME and DOCKERHUB_PULL_TOKEN.

Update system details

Architecture, workflow, and all environment variables of the update system are described in the Update Guide.

On-Premise: Stack Managed Directly via docker compose

Many on-premise tenants do not start the stack via deploy.sh but directly via docker compose with an additional SSL overlay (commonly docker-compose.ssl.yml). In that case the deploy.sh automatisms do not apply — three things must be done manually:

  1. Set the Compose profiles in the host .env (otherwise the PostgreSQL and updater containers will not start):

    COMPOSE_PROFILES=v2-postgres,v2-updater
    
  2. Add the V2 overlay to the file list:

    docker compose -f docker-compose.yml -f docker-compose.ssl.yml -f docker-compose.v2.yml up -d
    
  3. Align the updater file list if the SSL overlay adds configuration to the V2 services themselves (e.g. VIRTUAL_HOST variables). By default the updater works with docker-compose.yml:docker-compose.v2.yml only — during an in-app update it would otherwise recreate the V2 containers without the overlay configuration:

    UPDATER_COMPOSE_FILE=docker-compose.yml:docker-compose.ssl.yml:docker-compose.v2.yml
    

    If the overlay only adds proxy/certificate services (like docker-compose.https.yml in this repository), the default is sufficient.


Docker Containers

After a successful deployment, the following additional containers are running:

Container Purpose Port
calserver-api-v2 Laravel 13 API Backend 8081 (internal)
calserver-frontend Nuxt 4 Frontend 3000 (internal)
redis Cache, Queue, Sessions 6379 (internal)
calserver-postgres PostgreSQL 16 — only with V2_DB_CONNECTION=pgsql 5432 (internal)
calserver-updater Update sidecar — only with UPDATER_TOKEN set 8080 (internal)

Check container status

Use docker compose ps to check the status of all containers. All containers must show the status running.


Set Up Database

After the first start, the database must be initialized:

docker compose exec calserver-api-v2 php artisan db:install --v1 --clean --force
Flag Description
--v1 Imports existing v1 data into the v2 schema
--clean Removes existing v2 tables before recreation
--force Runs the command without confirmation prompts (for scripting)

Data loss with --clean

The --clean flag deletes all existing v2 tables and recreates them. Only use this flag during initial installation or when a complete reset is desired.


Data Migration from V1 with MET/TRACK (Sybase ASA)

Many V1 installations are connected to a MET/TRACK database (Sybase SQL Anywhere / ASA) via SymmetricDS — the V1 MySQL tables are then a 1:1 replica of the MET/TRACK core tables. There are therefore two ways to migrate the existing data to V2:

Option A — from the V1 database (v1:sync)

Recommended when the V1 replica is up to date (SymmetricDS sync running). The data is taken from the V1 MySQL database; core tables are mapped to readable column names in the process (e.g. I4201 → asset_number):

# Preview without writing
docker compose exec calserver-api-v2 php artisan v1:sync --dry-run

# Full migration
docker compose exec calserver-api-v2 php artisan v1:sync

Calibration type on order line items

V1 keeps the calibration type of an order line in two columns: complexity (the one price rows and the surcharge are attached to) and calibration_type (prefilled from the catalogue article). The migration merges them — wherever complexity is empty, the value from calibration_type fills it. A maintained value is never overwritten, and the source column keeps its content. Without this step a line item would arrive in V2 carrying "DAkkS" while the DAkkS surcharge never applied.

At the end of every migration the same step runs once more across the existing rows, catching whatever bypassed the mapping (partial import, data migrated earlier). You can check at any time with php artisan articles:backfill-calibration-type (report; --apply writes).

The document files do not come along

v1:sync transfers the document records (table dms), not the files. In own mode the bytes stay behind in the V1 tree: the documents appear in every list while every download comes up empty. Fetch them once:

docker exec calserver-api-v2 php artisan dms:import-v1              # report, writes nothing
docker exec calserver-api-v2 php artisan dms:import-v1 --apply --force

The command overwrites nothing and is repeatable. In v1-tree mode it is not needed — V2 keeps using the same tree — and it refuses there, because source and target would be identical.

Report templates have the same gap: v1:sync brings the metadata, php artisan reports:import-templates fetches the JRXML packages and PDF stationery (source storage/app/reports, or --source=PATH).

Option B — directly from MET/TRACK (mettrack:import)

Reads the ASA database directly, without the detour via SymmetricDS and V1. The connection can either be entered in the admin interface (see "Option B (UI)" below) or configured in the host .env; the ASA server must be reachable from the Docker host via TDS (default port 2638):

METTRACK_DB_HOST=asa-server
METTRACK_DB_PORT=2638
METTRACK_DB_DATABASE=mtrack
METTRACK_DB_USERNAME=mt
METTRACK_DB_PASSWORD=<password>
# Optional:
# METTRACK_DB_DRIVER=dblib      # dblib (FreeTDS/TDS, default) or odbc
# METTRACK_DB_OWNER=mt          # table owner in ASA
# METTRACK_ODBC_DSN=            # only with METTRACK_DB_DRIVER=odbc

Then run ./deploy.sh so the API container picks up the variables at startup, and start the import:

# Check connection, modules, and row counts (imports nothing)
docker compose exec calserver-api-v2 php artisan mettrack:import --list

# Import all MET/TRACK modules
docker compose exec calserver-api-v2 php artisan mettrack:import --force

The import covers the MET/TRACK core tables customers, inventory, assethistory, calibration, results, results_raw, standards, repair, location, and userlist; unmapped columns end up in custom_fields. The import only reads from the source — MET/TRACK and V1 remain unchanged.

Option B (UI) — log in and select the server right in the interface

The initial migration is also available in the admin interface under Administration → Data Migration (source "MET/TRACK (Sybase ASA)", permission data_migration_admin; the superadmin always holds it, any other role is granted it via role management). There you can enter the server (host/port), database, and login directly — the METTRACK_DB_* variables in the .env are then unnecessary.

In the "MET/TRACK server access" panel, enter the connection type (dblib or ODBC DSN), host, port, database, user, and password, then click "Connect & test". On success the access is kept encrypted and server-side (no password in browser storage, never in the URL) and is discarded automatically after two hours of inactivity; "Disconnect" removes it immediately. If METTRACK_DB_* variables are also set, the UI values take precedence.

Remove credentials after the migration

The METTRACK_DB_* variables are only needed for the one-time initial migration. Remove them from the host .env after a successful migration.


Health Check

After installation, you can verify the API availability using the health endpoint:

curl -s https://your-domain.com/api/v2/health | jq

Expected response:

{
  "status": "ok",
  "version": "2.x.x",
  "database": "connected",
  "redis": "connected"
}

Health check failing?

Check in the following order:

  1. Are all containers running? → docker compose ps
  2. Are the environment variables correct? → docker compose exec calserver-api-v2 php artisan env
  3. Is the database reachable? → docker compose exec calserver-api-v2 php artisan db:monitor
  4. Check container logs → docker compose logs calserver-api-v2 --tail 50

Next Steps

After successful installation:

  1. Configuration — Adjust environment variables and routing
  2. Create Users — Set up initial administrators
  3. Configure Roles — Assign permissions
  4. Update Guide — In-app updates via the updater sidecar