(cdcs-production)=
# Production Deployment

This guide covers deploying NexusLIMS-CDCS to a production environment with proper security, SSL certificates, and data management.

## Prerequisites

### Required Knowledge

- Linux system administration
- Docker and Docker Compose
- Basic understanding of DNS and SSL/TLS certificates
- Basic PostgreSQL database administration
- Web server/reverse proxy concepts

### Required Access

- Root or sudo access to production server
- DNS management for your domain
- (Optional) CIFS/SMB or NFS for data files
- (Optional) SMTP server for application notifications

---

## System Requirements

### Minimum Hardware

| Resource | Minimum | Recommended |
|----------|---------|-------------|
| **CPU** | 4 cores | 8 cores |
| **RAM** | 8 GB | 16 GB |
| **Disk** | 20 GB SSD | 100+ GB SSD |
| **Network** | 100 Mbps | 1 Gbps |

### Software Requirements

| Software | Version |
|----------|---------|
| **OS** | Ubuntu 22.04 LTS or RHEL 8+ |
| **Docker** | 24.0+ |
| **Docker Compose** | 2.20+ |
| **Git** | 2.0+ |

### Network Requirements

- **Firewall**: Ports 80 and 443 open (for ACME certificate validation)
- **DNS**: Ability to create A records pointing to your server
- **Optional**: Firewall rules for database ports if external access needed

---

## Pre-Deployment Checklist

Before deploying, ensure you have:

- Production server provisioned and accessible via SSH
- Domain name(s) registered and DNS configured
- SSL certificate strategy determined (either self-managed certs or automatic ACME/Let's Encrypt)
- Data storage locations identified and accessible
- Backup strategy planned
- Strong passwords generated for database and Redis
- Django secret key generated (minimum 50 characters)
- (Optional) SMTP server configured for email notifications

---

## Domain and DNS Setup

### Required DNS Records

You need two subdomains (replace `nexuslims.example.com` with your domain):

1. **Main application**: `nexuslims.example.com`
2. **File server**: `files.nexuslims.example.com`

### DNS Configuration

Create A records pointing to your server's public IP:

```
nexuslims.example.com.       A    203.0.113.42
files.nexuslims.example.com. A    203.0.113.42
```

### Verify DNS Propagation

Before deployment, verify DNS is working:

```bash
dig nexuslims.example.com
dig files.nexuslims.example.com
```

Both should return your server's IP address.

---

## SSL Certificate Options

### Option 1: Automatic ACME (Recommended)

**Best for**: Production deployments with public network access

Caddy automatically obtains and renews certificates from Let's Encrypt:

1. Set `CADDY_ACME_EMAIL` in your `.env` file
2. Ensure ports 80 and 443 are accessible from the internet
3. Caddy handles everything else automatically

**Advantages:**
- Fully automated certificate issuance and renewal
- Free certificates from Let's Encrypt
- Trusted by all browsers
- No manual intervention required

### Option 2: Manual Certificates

**Best for**: Organizations with existing PKI or certificate vendors

1. Obtain certificates from your CA (you need `fullchain.pem` and `privkey.pem`)

2. Create certificate directory and copy certificates:
   ```bash
   mkdir -p /opt/nexuslims/certs
   chmod 700 /opt/nexuslims/certs
   cp fullchain.pem /opt/nexuslims/certs/
   cp privkey.pem /opt/nexuslims/certs/
   chmod 600 /opt/nexuslims/certs/*
   ```

3. Set the certificate path in your `.env` file:
   ```bash
   CADDY_CERTS_HOST_PATH=/opt/nexuslims/certs
   ```

   The `docker-compose.prod.yml` already includes a volume mount that uses this variable:
   ```yaml
   - ${CADDY_CERTS_HOST_PATH}:/etc/caddy/certs:ro
   ```

4. Update `caddy/Caddyfile.prod` to use the certificates:
   ```text
   https://{$DOMAIN} {
       tls /etc/caddy/certs/fullchain.pem /etc/caddy/certs/privkey.pem
       # ... rest of config
   }

   https://{$FILES_DOMAIN} {
       tls /etc/caddy/certs/fullchain.pem /etc/caddy/certs/privkey.pem
       # ... rest of config
   }
   ```

```{note}
When using manual certificates, you are responsible for renewing them before expiration.
```

---

## Environment Configuration

### 1. Clone Repository

```bash
sudo mkdir -p /opt/nexuslims
cd /opt/nexuslims
git clone https://github.com/datasophos/NexusLIMS-CDCS.git
cd NexusLIMS-CDCS/deployment
```

### 2. Create Environment File

```bash
cp .env.prod.example .env
chmod 600 .env
```

### 3. Configure Environment Variables

Edit `.env` with your production values. See {doc}`configuration` for detailed documentation of all variables.

**Critical variables to set:**

```bash
# Domain configuration
DOMAIN=nexuslims.example.com
FILES_DOMAIN=files.nexuslims.example.com

# Security (generate strong values!)
SECRET_KEY=your-50-character-minimum-secret-key
POSTGRES_PASS=strong-database-password
REDIS_PASS=strong-redis-password

# ACME certificate email
CADDY_ACME_EMAIL=admin@example.com

# File paths (adjust to your storage locations)
NX_DATA_HOST_PATH=/mnt/nexuslims/data
NX_INSTRUMENT_DATA_HOST_PATH=/mnt/nexuslims/instrument-data
NX_CDCS_BACKUPS_HOST_PATH=/opt/nexuslims/backups
```

```{warning}
Never commit `.env` to version control! It contains secrets.
```

---

## File Storage Setup

NexusLIMS-CDCS serves two types of files:
- **NexusLIMS data**: Thumbnail images, metadata files
- **Instrument data**: Raw microscopy data files

### Option 1: Local Storage

```bash
sudo mkdir -p /mnt/nexuslims/data
sudo mkdir -p /mnt/nexuslims/instrument-data
sudo chown -R $USER:$USER /mnt/nexuslims
sudo chmod -R 755 /mnt/nexuslims
```

### Option 2: NFS Mount

```bash
# Install NFS client
sudo apt-get install nfs-common  # Ubuntu/Debian

# Create mount points
sudo mkdir -p /mnt/nexuslims/data
sudo mkdir -p /mnt/nexuslims/instrument-data

# Mount NFS shares
sudo mount -t nfs nfs-server:/export/nexuslims/data /mnt/nexuslims/data
sudo mount -t nfs nfs-server:/export/nexuslims/instrument-data /mnt/nexuslims/instrument-data

# Add to /etc/fstab for persistence
echo "nfs-server:/export/nexuslims/data /mnt/nexuslims/data nfs defaults 0 0" | sudo tee -a /etc/fstab
echo "nfs-server:/export/nexuslims/instrument-data /mnt/nexuslims/instrument-data nfs defaults 0 0" | sudo tee -a /etc/fstab
```

### Option 3: CIFS/SMB Mount

```bash
# Install CIFS utilities
sudo apt-get install cifs-utils  # Ubuntu/Debian

# Create credentials file
sudo mkdir -p /etc/smbcredentials
sudo touch /etc/smbcredentials/nexuslims.cred
sudo chmod 600 /etc/smbcredentials/nexuslims.cred

sudo tee /etc/smbcredentials/nexuslims.cred > /dev/null << EOF
username=your_smb_username
password=your_smb_password
domain=WORKGROUP
EOF

# Create mount points and mount
sudo mkdir -p /mnt/nexuslims/data
sudo mkdir -p /mnt/nexuslims/instrument-data

sudo mount -t cifs //nas-server/nexuslims-data /mnt/nexuslims/data \
  -o credentials=/etc/smbcredentials/nexuslims.cred,uid=$(id -u),gid=$(id -g)

sudo mount -t cifs //nas-server/nexuslims-instrument-data /mnt/nexuslims/instrument-data \
  -o credentials=/etc/smbcredentials/nexuslims.cred,uid=$(id -u),gid=$(id -g)
```

---

## Deployment

### 1. Load Admin Commands

```bash
cd /opt/nexuslims/NexusLIMS-CDCS/deployment
source admin-commands.sh
```

This provides the `dc-prod` alias (shortcut for `docker compose -f docker-compose.base.yml -f docker-compose.prod.yml`).

### 2. Build and Pull Images

```bash
dc-prod build  # Build CDCS and Caddy images
dc-prod pull   # Pull PostgreSQL and Redis images
```

### 3. Start Services

```bash
dc-prod up -d
```

### 4. Monitor Startup

```bash
# Watch logs
dc-prod logs -f

# Check service status
dc-prod ps
```

All services should show `Up` and `healthy`.

### 5. Verify Certificate Acquisition

If using ACME, watch Caddy logs for certificate issuance:

```bash
dc-prod logs -f caddy
```

You should see: `certificate obtained successfully`

---

## Post-Deployment Setup

### 1. Run Initialization Script

The initialization script sets up the superuser, schema, and XSLT stylesheets:

```bash
admin-init
```

This will:
1. Verify database migrations are complete
2. Create superuser (prompts for credentials)
3. Upload NexusLIMS schema
4. Load and configure XSLT stylesheets
5. Load data exporters

### 2. Verify Installation

```bash
admin-stats
```

Expected output:
```
============================================================
NexusLIMS-CDCS System Statistics
============================================================

Users:
  Total:      1
  Superusers: 1

Templates:
  Total: 1
    - Nexus Experiment Schema (Version 1)

XSLT Stylesheets:
  Total: 2
    - detail_stylesheet.xsl
    - list_stylesheet.xsl

============================================================
```

### 3. Test File Serving

Navigate to:
- `https://files.nexuslims.example.com/data/`
- `https://files.nexuslims.example.com/instrument-data/`

Both should show directory listings.

### 4. Create Backup Directory

```bash
sudo mkdir -p /opt/nexuslims/backups
sudo chown $USER:$USER /opt/nexuslims/backups
```

```{important}
The backup directory must be owned by the user running Docker, not root. Otherwise backup scripts will fail with permission errors.
```

Restart services to pick up the mount:
```bash
dc-prod down
dc-prod up -d
```

### 5. Configure Automated Backups

Create a daily backup script:

```bash
cat > /opt/nexuslims-backup.sh << 'EOF'
#!/bin/bash
set -e

cd /opt/nexuslims/NexusLIMS-CDCS/deployment
source admin-commands.sh

# Run backup
admin-backup

# Also create a database dump
admin-db-dump

# Remove backups older than 30 days
find /opt/nexuslims/backups -type d -name "backup_*" -mtime +30 -exec rm -rf {} \; 2>/dev/null || true
find /opt/nexuslims/backups -type f -name "backup_*.sql" -mtime +30 -delete 2>/dev/null || true
EOF

chmod +x /opt/nexuslims-backup.sh

# Add to crontab (daily at 2 AM)
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/nexuslims-backup.sh") | crontab -
```

---

## Security Hardening

### Firewall Configuration

**UFW (Ubuntu):**
```bash
sudo ufw allow 22/tcp   # SSH
sudo ufw allow 80/tcp   # HTTP (for ACME)
sudo ufw allow 443/tcp  # HTTPS
sudo ufw enable
```

**firewalld (RHEL):**
```bash
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
```

### Database Security

- Don't expose PostgreSQL port externally unless necessary
- Use strong passwords (already set in `.env`)
- Keep PostgreSQL container updated

### Container Security

Keep images updated regularly:
```bash
dc-prod pull
dc-prod up -d
```

### Log Rotation

Configure Docker log rotation in `/etc/docker/daemon.json`:

```json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
```

Restart Docker: `sudo systemctl restart docker`

---

(cdcs-production-upgrading)=
## Upgrading

### Application Updates

```{important}
Releases that include a schema change require one extra step after rebuild:
`admin-upgrade-schema` adds the new schema version to the database, migrates
all existing records to it, and refreshes the XSLT stylesheets. It is safe
to run on every upgrade — if nothing changed, it exits cleanly.
```

1. **Backup current deployment:**
   ```bash
   source admin-commands.sh
   admin-backup
   ```

2. **Pull latest code and check out the release tag:**
   ```bash
   cd /opt/nexuslims/NexusLIMS-CDCS
   git fetch
   git checkout v3.21.0-nx1  # or desired version (downgrades are not supported)
   ```

3. **Review the changelog** for breaking changes and new environment variables

4. **Apply any new environment variables** from `.env.prod.example` to your `.env` file

5. **Rebuild and restart:**
   ```bash
   cd deployment
   source admin-commands.sh
   dc-prod build
   dc-prod down
   dc-prod up -d
   ```

6. **Run Django database migrations:**
   ```bash
   docker exec nexuslims_prod_cdcs python manage.py migrate
   ```

7. **Upgrade schema, records, and XSLT:**
   ```bash
   source admin-commands.sh
   admin-upgrade-schema
   ```

   Expected output when a schema change is present:
   ```text
   Step 1/3: Schema check
     → On-disk hash differs from active version — adding Version 2
     ✓ Added Version 2 to "Nexus Experiment Schema"
     ✓ Version 2 set as current

   Step 2/3: Record migration
     ✓ Migrated 42 record(s) to current schema version

   Step 3/3: XSLT update
     ✓ detail_stylesheet.xsl updated
     ✓ list_stylesheet.xsl updated

   Schema upgrade complete.
   ```

   Expected output when nothing changed:
   ```text
   Step 1/3: Schema check
     ✓ Active version hash matches on-disk schema — no upgrade needed

   Step 2/3: Record migration
     ✓ All records already on current version — no migration needed

   Step 3/3: XSLT update
     ✓ detail_stylesheet.xsl updated
     ✓ list_stylesheet.xsl updated

   Nothing to upgrade.
   ```

8. **Verify:**
   ```bash
   admin-stats
   ```

### Manual upgrade via web UI (fallback)

Use this path if the script does not work or you want to verify each step interactively.

**Step 1 — Upload new schema version**

Navigate to `https://<your-domain>/staff-core-admin/templates`, click on the "Versions"
button for "Nexus Experiment Schema", then click "Upload New Version" button to upload the updated
`nexus-experiment.xsd` from `deployment/schemas/`.

**Step 2 — Migrate records**

After uploading the version, you will be redirected to `https://<your-domain>/staff-core-admin/template/data-migration`
to migrate records from the previous version to the one you just uploaded. It should be safe to just click "Migrate"
without anything else

- **1. Source template:** select the old version(s)
- **2. Select data:** select all existing records
- **3. XSLT:** leave blank — no XML transformation is needed for purely additive schema changes
- **4. Target template:** Choose the latest version, which should be what you just uploaded 
- Click **Migrate** and wait for the progress bar to complete

**Step 3 — Set new version as current**

Navigate back to `https://<your-domain>/staff-core-admin/templates`, click on the "Versions"
button for "Nexus Experiment Schema", then click "Set Current" on the latest version you just uploaded.

**Update XSLT**

This is most easily done from the admin script. Run:

```bash
cd deployment
source admin-commands.sh
admin-update-xslt
```

### Rollback

If upgrade fails:

```bash
dc-prod down
git checkout v3.20.0-nx0  # or whichever version matches your NexusLIMS backend — see {ref}`compatibility`
cd deployment
dc-prod build
dc-prod up -d
```

If database schema changed, restore from backup:
```bash
admin-db-restore /opt/nexuslims/backups/backup_20260109_120000.sql
```

---

## Next Steps

- {doc}`configuration` - Detailed environment variable reference
- {doc}`administration` - Backup, restore, and maintenance procedures
- {doc}`troubleshooting` - Common issues and solutions
- {doc}`local-test-deployment` - Test production config locally with mkcert
