Skip to content

calServer v2 — Installation Guide

Prerequisites

  • Docker >= 24.0 and Docker Compose >= 2.20
  • calServer v1 is already running (docker-compose.yml)
  • Domain points to the server via DNS (A record)
  • Ports 80 and 443 open (for Let's Encrypt HTTP-01 challenge)
  • At least 1 GB additional RAM (API v2 ~200 MB, frontend ~100 MB, Redis ~50 MB)

Quick Start (Demo/Development Server)

1. Configure .env

cd /var/calserver

Add the following variables to the existing .env:

# Enable v2 services
ENABLE_V2=true

# Generate Laravel app key (one-time):
LARAVEL_APP_KEY=base64:$(docker run --rm php:8.3-cli php -r "echo base64_encode(random_bytes(32));")

# Environment
APP_ENV=production
APP_DEBUG=false
APP_URL=https://develop.net-cal.com  # Same domain as v1!
DB_PREFIX=                            # table prefix of the V1 database, if any

Important: APP_URL must match the DOMAIN variable. v2 does not run on a separate domain but under /v2/ and /api/v2/.

2. Generate Laravel App Key

# Option A: Via Docker
docker run --rm php:8.3-cli php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"

# Option B: If the Laravel container is already running
docker exec calserver-api-v2 php artisan key:generate --show

Enter the generated key in .env as LARAVEL_APP_KEY.

3. Pull Docker Images

# Manually (without CI/CD)
docker pull calhelp/calserver-api-v2:release-latest
docker pull calhelp/calserver-frontend:release-latest

# Or a specific tag
docker pull calhelp/calserver-api-v2:develop-latest
docker pull calhelp/calserver-frontend:develop-latest

4. Start Deployment

sudo ./deploy.sh

The script automatically detects ENABLE_V2=true and: 1. Appends docker-compose.v2.yml as an overlay 2. Starts the v2 services (API, frontend, Redis) 3. Runs health checks for all services

5. Verify

# v1 (unchanged)
curl -s https://develop.net-cal.com/ | head -5

# v2 API health check
curl -s https://develop.net-cal.com/api/v2/health
# → {"status":"ok"}

# v2 Frontend
curl -s -o /dev/null -w "%{http_code}" https://develop.net-cal.com/v2/
# → 200

# Container status
docker ps --filter name=calserver --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

Expected output:

NAMES                 STATUS          PORTS
calserver             Up 2 minutes    0.0.0.0:8080->8080/tcp
calserver-api-v2      Up 2 minutes    8081/tcp
calserver-frontend    Up 2 minutes    3000/tcp
calserver-redis       Up 2 minutes    6379/tcp

Let's Encrypt SSL

How It Works

v2 uses the same SSL certificate as v1:

Internet
   │
   ├── https://develop.net-cal.com/          → calserver (v1, Yii)
   ├── https://develop.net-cal.com/api/v2/   → calserver-api-v2 (Laravel)
   └── https://develop.net-cal.com/v2/       → calserver-frontend (Nuxt)
   │
   ▼
┌─────────────────────────────────────────────┐
│ nginx-proxy (nginxproxy/nginx-proxy)          │
│   - SSL termination via Let's Encrypt        │
│   - Certificate: *.net-cal.com               │
│   - vhost.d/default_location:                │
│     /api/v2/* → calserver-api-v2:8081        │
│     /v2/*     → calserver-frontend:3000      │
│     /*        → calserver:8080 (default)     │
└─────────────────────────────────────────────┘

No Additional Certificate Needed

The nginx-proxy container with acme-companion: 1. Detects LETSENCRYPT_HOST and VIRTUAL_HOST from the calserver container 2. Requests a certificate from Let's Encrypt (HTTP-01 challenge) 3. Stores it under /etc/nginx/certs/ 4. Automatically configures HTTPS for the VIRTUAL_HOST 5. The vhost.d/default_location file is automatically included in the SSL server block 6. Result: /api/v2/* and /v2/* are immediately reachable via HTTPS

Check Configuration

# Check certificate
docker exec nginx-proxy ls -la /etc/nginx/certs/ | grep net-cal

# Check vhost.d location
docker exec nginx-proxy cat /etc/nginx/vhost.d/default_location

# Test Nginx config
docker exec nginx-proxy nginx -T 2>&1 | grep -A5 "location /api/v2"

Let's Encrypt Staging (Test Server)

On test servers, the Let's Encrypt staging environment should be used to avoid hitting the rate limit (5 certificates per week per domain).

Set in .env:

LETSENCRYPT_TEST=true

The acme-companion then uses the staging server (acme-staging-v02.api.letsencrypt.org). Staging certificates are shown as "not trusted" in the browser (Issuer: "Fake LE Intermediate X1"), but the SSL chain works correctly.

For production servers, omit the variable or leave it empty:

#LETSENCRYPT_TEST=

Let's Encrypt Renewal

Renewal happens automatically via the acme-companion container. If the calserver container has LETSENCRYPT_RESTART_CONTAINER=true, nginx-proxy is also reloaded upon renewal.

Building Manually (Without CI/CD)

If images should be built locally:

# From the repository root:

# API v2 image
docker build -f Dockerfile.laravel -t calhelp/calserver-api-v2:local .

# Frontend v2 image
docker build -f Dockerfile.frontend -t calhelp/calserver-frontend:local .

Environment Variables Reference

Required (in .env)

Variable Description Example
ENABLE_V2 Enable v2 services true
LARAVEL_APP_KEY Laravel encryption key base64:abc123...
APP_URL Base URL (must match DOMAIN) https://develop.net-cal.com
DB_PREFIX Table prefix of the V1 database (empty if no prefix)

Optional (Defaults Provided)

Variable Default Description
V2_STANDALONE false true = run V2 without V1: docker-compose.yml is not loaded at all, so neither the Yii container nor the V1 MySQL is started. Implies ENABLE_V2=true. See V2 autonomy check
LETSENCRYPT_TEST (empty) true = Let's Encrypt staging (no rate limit)
APP_ENV production Laravel environment
APP_DEBUG false Debug mode (never true in production!)
PUSHER_APP_ID (from v1) For live updates
PUSHER_KEY (from v1) For live updates
PUSHER_SECRET (from v1) For live updates
PUSHER_CLUSTER (from v1) For live updates

Already Present (Shared with v1)

These variables are automatically passed through to v2: - DB_HOST, DB_DATABASE, DB_USER, DB_PASSWORD (MySQL) - DOMAIN, FILE_MANAGER_PATH - PUSHER_* (real-time updates)

Disabling v2

# In .env:
ENABLE_V2=false

# Redeploy:
sudo ./deploy.sh

# Or manually stop v2 containers:
docker stop calserver-api-v2 calserver-frontend calserver-redis
docker rm calserver-api-v2 calserver-frontend calserver-redis

v1 then continues to run on its own — with no impact.

Troubleshooting

v2 API Not Responding

# Check container logs
docker logs calserver-api-v2 --tail 50

# Common cause: LARAVEL_APP_KEY is missing or invalid
docker exec calserver-api-v2 php artisan key:generate --show

# Is PHP-FPM running?
docker exec calserver-api-v2 ps aux | grep php-fpm

Frontend Shows Blank Page

# Check container logs
docker logs calserver-frontend --tail 50

# API base URL correct?
docker exec calserver-frontend printenv | grep NUXT
# NUXT_PUBLIC_API_BASE should be https://develop.net-cal.com/api/v2

Redis Connection Failed

# Is Redis running?
docker exec calserver-redis redis-cli ping
# → PONG

# Reachable from the API container?
docker exec calserver-api-v2 php -r "echo @fsockopen('redis', 6379) ? 'OK' : 'FAIL';"

Let's Encrypt Certificate Missing

# Check companion logs
docker logs letsencrypt-nginx-proxy --tail 30

# DNS correct?
dig +short develop.net-cal.com

# Port 80 open? (for HTTP-01 challenge)
curl -v http://develop.net-cal.com/.well-known/acme-challenge/test 2>&1 | head -20

Check Nginx Proxy Routing

# Show active Nginx config
docker exec nginx-proxy nginx -T 2>&1 | grep -B2 -A10 "location /api/v2"

# Check vhost.d files
docker exec nginx-proxy ls -la /etc/nginx/vhost.d/

# Reload Nginx (without restart)
docker exec nginx-proxy nginx -s reload

CI/CD Pipeline

The v2 services are automatically deployed by CI/CD:

  1. Push to develop → Images built and deployed with tag develop-latest
  2. Push to master/release/* → Images built and deployed with tag release-latest

Pipeline Flow

┌───────────────────────────────────────────────────┐
│ GitHub Actions CI                                  │
├───────────────────────────────────────────────────┤
│                                                   │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────┐ │
│  │ build-v1    │  │ build-api-v2 │  │ build-fe │ │
│  │ (Yii)       │  │ (Laravel)    │  │ (Nuxt)   │ │
│  └──────┬──────┘  └──────┬───────┘  └────┬─────┘ │
│         │                │               │        │
│         └────────┬───────┴───────────────┘        │
│                  ▼                                 │
│         ┌────────────────┐                        │
│         │  deploy-dev    │ (SSH → deploy.sh)       │
│         │  deploy-demo   │                        │
│         └────────────────┘                        │
└───────────────────────────────────────────────────┘

The deploy.sh script detects ENABLE_V2=true on the target server and automatically starts all v2 services as well.