Tailwind css - managing a multi-domain VPS, and the Production Way .
Just for css, you need to have nodejs and npm:
sudo apt update
sudo apt install nodejs npm
node -v
npm -v
Since I am managing a multi-domain VPS with Virtualenvs and Systemd, the setup that is isolated, predictable, and doesn't waste CPU resources.
Here is your step-by-step SRE guide to moving from the Tailwind CDN to a production-grade NPM setup.
Log into your VPS (or local machine), go to your project folder (
where manage.py lives), and initialize the environment.
# 1. Create package.json to track dependencies locally
npm init -y
# 2. Install Tailwind as a development dependency
npm install tailwindcss@latest
# 3. Create the tailwind.config.js file
npx tailwindcss init
node ./node_modules/tailwindcss/lib/cli.js -i ./static/src/input.css -o ./static/dist/css/output.css --minify
Explanation: This treats Tailwind as a local project asset. Domain A can use one version of Tailwind while Domain B uses another, and they will never conflict.
Step 2: Configure tailwind.config.js
Edit the newly created tailwind.config.js. You must tell Tailwind exactly where your Django HTML files are so it can scan them for classes.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./templates/**/*.html", // Global templates
"./**/templates/**/*.html", // App-specific templates (exam, mcq, etc.)
"./static/**/*.js", // Any dynamic JS classes
],
theme: {
extend: {
borderRadius: {
'4xl': '2rem',
'5xl': '2.5rem',
}
},
},
plugins: [],
}
Explanation: Tailwind only generates CSS for the classes it actually "sees" in these files. This keeps your final CSS file tiny (usually < 20KB).
Step 3: Create the Input File
Create the folder structure and the base CSS file.
mkdir -p static/src
touch static/src/input.css
Add this to static/src/input.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
/* SRE Safety Fix for your Invisible Question bug */
.question-view {
display: none;
}
Step 4: Add Build Script to package.json
To make your deployment easier, add a shortcut for the build command. Open package.json and edit the "scripts" section:
"scripts": {
"build": "tailwindcss -i ./static/src/input.css -o ./static/dist/css/output.css --minify"
}
Explanation: This creates a physical file at static/dist/css/output.css. This is the file Nginx will serve.
Step 5: Update base.html
Now, swap the CDN for your local file.
HTML
{% load static %}
<head>
<link rel="stylesheet" href="{% static 'dist/css/output.css' %}">
</head>
Step 6: The "Production Way" Deployment
Since you use Systemd and Virtualenvs, you need to run these commands every time you change your UI or deploy.
# 1. Build the CSS
npm run build
# 2. Collect static files for Nginx
# Django moves everything from /static/ to /staticfiles/
python manage.py collectstatic --noinput
# 3. Restart your domain-specific service
sudo systemctl restart gunicorn_yourdomain
Explanation: Why this architecture?
Isolation (Multi-Domain): Because we installed Tailwind in the project folder, Domain A and Domain B are totally separate. You can move the folder to a new VPS, and all you need is npm install and npm run build to restore the UI.
Zero Race Conditions: The "Invisible Question" bug happened because the CDN script was loading after the page rendered. With this method, the CSS is loaded immediately. The browser knows .question-view is hidden before the first pixel is painted.
VPS Performance: Your server no longer has to fetch an external script from the internet (CDN) on every page load. Nginx serves the local output.css with a high cache header, making your exam engine feel instant.
---
Detail explanation
Managing a multi-domain VPS, the Production Way is all about isolation and portability.
Each project to be a self-contained unit so that if you move one domain to a new server, you aren't untangling a web of global dependencies.
Here is the professional architecture for managing Tailwind and Django on a multi-site VPS.
Local Project Architecture
Don't install Tailwind globally on the VPS.
Instead, treat it as a project-level (where your manage.py file is present) dependency. This ensures that Domain A can use Tailwind v3 while Domain B (perhaps an older project) stays on v2 without conflicts.
Step 1: Initialize Node in the Project Root
On your local machine (and eventually on the VPS), run:
Bash
cd /home/ubuntu/your-project/
npm init -y
npm install -D tailwindcss
npx tailwindcss init
Step 2: Configure for Portability
In your tailwind.config.js, use relative paths.
This makes migration easy because the path remains valid regardless of whether the project lives in /home/ubuntu/ or /var/www/.
JavaScript
module.exports = {
content: [
"./templates/**/*.html",
"./**/templates/**/*.html",
"./static/js/**/*.js",
],
theme: {
extend: {
borderRadius: { '4xl': '2rem' }
},
},
plugins: [],
}
2. The Build Pipeline
Since you are on a VPS, you don't want to run the "watcher" (which consumes RAM). You want to build a static asset.
Step 3: Create a Build Script
Add this to your package.json:
JSON
"scripts": {
"build": "tailwindcss -i ./static/src/input.css -o ./static/dist/css/output.css --minify"
}
Step 4: Django Integration
In your settings.py, ensure your static paths are isolated:
Python
STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'staticfiles' # For production collection
STATICFILES_DIRS = [BASE_DIR / 'static']
3. The Multi-Domain VPS Deployment
To make migration "Easy," you should use a Deployment Script. This standardizes how every domain on your VPS is updated.
Step 5: Create deploy.sh
Create this in your project root. When you move to a new VPS, you just run this one script.
bash
#!/bin/bash
# 1. Pull latest code
git pull origin main
# 2. Build Tailwind CSS (The Production Way)
# This generates the physical file Nginx will serve
npm install
npm run build
# 3. Django maintenance
source venv/bin/activate
pip install -r requirements.txt
python3 manage.py migrate
python3 manage.py collectstatic --noinput
# 4. SRE Safety: Restart only this domain's service
sudo systemctl restart gunicorn_yourdomain
Since you are sticking with Virtualenvs and Systemd, you’re following a classic, reliable SRE pattern.
To move away from the CDN and use your project-level compiled Tailwind,
you only need to make three specific changes: one in your settings.py, one in your base.html, and a final Build step.
You need to ensure Django knows where the new compiled CSS file lives. Since you're using collectstatic for production, configure your paths like this:
Update base.html
Remove the script tag for the CDN and replace it with the standard Django static template tag.
Before (CDN):
After (Production Way):
Create this file at static/src/input.css:
/* 1. Core Tailwind Directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 2. SRE/Stability Layer */
@layer base {
/* Prevents the "Invisible Question" bug by ensuring
the display state is set before JS even loads.
*/
.question-view {
display: none;
}
/* Standardizes the font-smoothing for heavy black titles */
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@apply bg-gray-50 text-gray-900;
}
}
/* 3. Component Layer (Optional) */
@layer components {
/* If you find yourself using 'rounded-[2rem] border border-gray-100 shadow-sm'
everywhere, you can create a reusable 'card' class here.
*/
.exam-card {
@apply bg-white p-6 rounded-4xl border border-gray-100 shadow-sm;
}
.nav-dot-active {
@apply border-blue-600 scale-110 shadow-md ring-4 ring-blue-500/20;
}
}
/* 4. Custom Utilities */
@layer utilities {
/* Custom spacing or specific animations for the News Ticker
or Kalahandi Bar Association site can go here. */
.tabular-nums {
font-variant-numeric: tabular-nums;
}
}
The "Production Way" Command
Once that file is saved, run this command to generate your actual CSS file. This command is what you will eventually put in your deploy.sh:
Bash
npx tailwindcss -i ./static/src/input.css -o ./static/dist/css/output.css --minify
replace:
<script src="https://cdn.tailwindcss.com"></script>`
to
<link rel="stylesheet" href="{% static 'dist/css/output.css' %}">
Pro-Tip for your VPS Architecture
Because you are using Systemd, you can actually add the Tailwind build command to your deployment workflow so it's impossible to forget.
When you run python manage.py collectstatic, Django will pick up that static/dist/css/output.css and move it to your staticfiles/ folder where Nginx is waiting to serve it.
NOTE: tailwind.config.js pointing to the correct template folders? (If not, the output.css will be empty!)