Initial infrastructure setup
This commit is contained in:
commit
91f4ba3dbf
7 changed files with 754 additions and 0 deletions
17
.env.example
Normal file
17
.env.example
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# PostgreSQL
|
||||
POSTGRES_PASSWORD=change-me-to-a-strong-password
|
||||
|
||||
# Cloudflare DNS Challenge
|
||||
CLOUDFLARE_DNS_API_TOKEN=your-cloudflare-api-token-here
|
||||
ACME_EMAIL=lorentz@wulfconsulting.com
|
||||
|
||||
# Domains
|
||||
DEV_DOMAIN=dev.quest.vorteq.wulf.cloud
|
||||
TEST_DOMAIN=testing.vorteq.wulf.cloud
|
||||
PROD_DOMAIN=quest.vorteq.wulf.cloud
|
||||
TRAEFIK_DOMAIN=traefik.vorteq.wulf.cloud
|
||||
|
||||
# Better-Auth Secrets (generate with: openssl rand -base64 32)
|
||||
DEV_AUTH_SECRET=your-dev-secret-here
|
||||
TEST_AUTH_SECRET=your-test-secret-here
|
||||
PROD_AUTH_SECRET=your-prod-secret-here
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.env
|
||||
traefik/acme.json
|
||||
traefik/logs/
|
||||
*.log
|
||||
node_modules/
|
||||
403
create-files.sh
Executable file
403
create-files.sh
Executable file
|
|
@ -0,0 +1,403 @@
|
|||
#!/bin/bash
|
||||
# Run this from /opt/stacks/vorteq on expvtcasp01
|
||||
# Creates all infrastructure files for the Vorteq Quest Portal
|
||||
|
||||
set -e
|
||||
echo "Creating infrastructure files..."
|
||||
|
||||
# ============================================================
|
||||
# docker-compose.yml
|
||||
# ============================================================
|
||||
cat > docker-compose.yml << 'DCOMPOSE'
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Traefik Reverse Proxy
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- web
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- CF_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./traefik/traefik.yml:/traefik.yml:ro
|
||||
- ./traefik/acme.json:/acme.json
|
||||
- ./traefik/config:/config:ro
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN}`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.middlewares=auth"
|
||||
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$8evjzm8w$$FU3G.qPxZRGlnUKzPJ6QB/"
|
||||
|
||||
# PostgreSQL
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: postgres
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_MULTIPLE_DATABASES: vorteq_dev,vorteq_test,vorteq_prod
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Redis
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Development Environment
|
||||
app-dev:
|
||||
image: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq:dev
|
||||
container_name: vorteq-dev
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/vorteq_dev
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
BETTER_AUTH_SECRET: ${DEV_AUTH_SECRET}
|
||||
BETTER_AUTH_URL: https://${DEV_DOMAIN}
|
||||
PORT: 3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.app-dev.rule=Host(`${DEV_DOMAIN}`)"
|
||||
- "traefik.http.routers.app-dev.entrypoints=websecure"
|
||||
- "traefik.http.routers.app-dev.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.app-dev.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.app-dev.middlewares=dev-headers"
|
||||
- "traefik.http.middlewares.dev-headers.headers.customresponseheaders.X-Environment=development"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
# Testing Environment
|
||||
app-test:
|
||||
image: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq:test
|
||||
container_name: vorteq-test
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
NODE_ENV: test
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/vorteq_test
|
||||
REDIS_URL: redis://redis:6379/1
|
||||
BETTER_AUTH_SECRET: ${TEST_AUTH_SECRET}
|
||||
BETTER_AUTH_URL: https://${TEST_DOMAIN}
|
||||
PORT: 3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.app-test.rule=Host(`${TEST_DOMAIN}`)"
|
||||
- "traefik.http.routers.app-test.entrypoints=websecure"
|
||||
- "traefik.http.routers.app-test.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.app-test.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.app-test.middlewares=test-headers"
|
||||
- "traefik.http.middlewares.test-headers.headers.customresponseheaders.X-Environment=testing"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
# Production Environment
|
||||
app-prod:
|
||||
image: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq:latest
|
||||
container_name: vorteq-prod
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/vorteq_prod
|
||||
REDIS_URL: redis://redis:6379/2
|
||||
BETTER_AUTH_SECRET: ${PROD_AUTH_SECRET}
|
||||
BETTER_AUTH_URL: https://${PROD_DOMAIN}
|
||||
PORT: 3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.app-prod.rule=Host(`${PROD_DOMAIN}`)"
|
||||
- "traefik.http.routers.app-prod.entrypoints=websecure"
|
||||
- "traefik.http.routers.app-prod.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.app-prod.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.app-prod.middlewares=prod-chain"
|
||||
- "traefik.http.middlewares.prod-chain.chain.middlewares=security-headers,rate-limit,compress"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsPreload=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.forceSTSHeader=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
|
||||
- "traefik.http.middlewares.compress.compress=true"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
DCOMPOSE
|
||||
echo " docker-compose.yml"
|
||||
|
||||
# ============================================================
|
||||
# traefik/traefik.yml
|
||||
# ============================================================
|
||||
mkdir -p traefik/{config,logs}
|
||||
|
||||
cat > traefik/traefik.yml << 'TRAEFIK'
|
||||
api:
|
||||
dashboard: true
|
||||
insecure: false
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: ":443"
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
exposedByDefault: false
|
||||
network: web
|
||||
file:
|
||||
directory: /config
|
||||
watch: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: lorentz@wulfconsulting.com
|
||||
storage: acme.json
|
||||
dnsChallenge:
|
||||
provider: cloudflare
|
||||
delayBeforeCheck: 0
|
||||
resolvers:
|
||||
- "1.1.1.1:53"
|
||||
- "8.8.8.8:53"
|
||||
|
||||
log:
|
||||
level: INFO
|
||||
|
||||
accessLog:
|
||||
filePath: /var/log/traefik/access.log
|
||||
bufferingSize: 100
|
||||
TRAEFIK
|
||||
echo " traefik/traefik.yml"
|
||||
|
||||
# ============================================================
|
||||
# init-databases.sh
|
||||
# ============================================================
|
||||
cat > init-databases.sh << 'INITDB'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE DATABASE vorteq_dev;
|
||||
GRANT ALL PRIVILEGES ON DATABASE vorteq_dev TO postgres;
|
||||
|
||||
CREATE DATABASE vorteq_test;
|
||||
GRANT ALL PRIVILEGES ON DATABASE vorteq_test TO postgres;
|
||||
|
||||
CREATE DATABASE vorteq_prod;
|
||||
GRANT ALL PRIVILEGES ON DATABASE vorteq_prod TO postgres;
|
||||
EOSQL
|
||||
|
||||
echo "Multiple databases created successfully!"
|
||||
INITDB
|
||||
chmod +x init-databases.sh
|
||||
echo " init-databases.sh"
|
||||
|
||||
# ============================================================
|
||||
# .env.example
|
||||
# ============================================================
|
||||
cat > .env.example << 'ENVEX'
|
||||
# PostgreSQL
|
||||
POSTGRES_PASSWORD=change-me-to-a-strong-password
|
||||
|
||||
# Cloudflare DNS Challenge
|
||||
CLOUDFLARE_DNS_API_TOKEN=your-cloudflare-api-token-here
|
||||
ACME_EMAIL=lorentz@wulfconsulting.com
|
||||
|
||||
# Domains
|
||||
DEV_DOMAIN=dev.quest.vorteq.wulf.cloud
|
||||
TEST_DOMAIN=testing.vorteq.wulf.cloud
|
||||
PROD_DOMAIN=quest.vorteq.wulf.cloud
|
||||
TRAEFIK_DOMAIN=traefik.vorteq.wulf.cloud
|
||||
|
||||
# Better-Auth Secrets (generate with: openssl rand -base64 32)
|
||||
DEV_AUTH_SECRET=your-dev-secret-here
|
||||
TEST_AUTH_SECRET=your-test-secret-here
|
||||
PROD_AUTH_SECRET=your-prod-secret-here
|
||||
ENVEX
|
||||
echo " .env.example"
|
||||
|
||||
# ============================================================
|
||||
# setup.sh
|
||||
# ============================================================
|
||||
cat > setup.sh << 'SETUP'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Setting up Traefik Multi-Environment Stack"
|
||||
echo "=============================================="
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo "Checking prerequisites..."
|
||||
|
||||
command -v docker >/dev/null 2>&1 || {
|
||||
echo -e "${RED}Docker is not installed${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v docker compose >/dev/null 2>&1 || {
|
||||
echo -e "${RED}Docker Compose is not installed${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo -e "${GREEN}Docker and Docker Compose are installed${NC}"
|
||||
|
||||
echo "Creating directory structure..."
|
||||
mkdir -p traefik/{config,logs}
|
||||
|
||||
echo "Setting up SSL certificate storage..."
|
||||
touch traefik/acme.json
|
||||
chmod 600 traefik/acme.json
|
||||
|
||||
chmod +x init-databases.sh
|
||||
|
||||
echo "Creating Docker network..."
|
||||
docker network create web 2>/dev/null || echo -e "${YELLOW}Network 'web' already exists${NC}"
|
||||
|
||||
if [ ! -f .env ]; then
|
||||
echo "Creating .env file..."
|
||||
cp .env.example .env
|
||||
echo -e "${YELLOW}Please edit .env file with your actual values!${NC}"
|
||||
else
|
||||
echo -e "${GREEN}.env file already exists${NC}"
|
||||
fi
|
||||
|
||||
SERVER_IP=$(curl -s ifconfig.me 2>/dev/null || echo "YOUR_SERVER_IP")
|
||||
echo ""
|
||||
echo "Cloudflare DNS Configuration Check"
|
||||
echo "===================================="
|
||||
echo "Make sure these DNS records are configured in Cloudflare:"
|
||||
echo " - quest.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo " - dev.quest.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo " - testing.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo " - traefik.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo ""
|
||||
echo "Important: Set DNS records to 'DNS only' (grey cloud)"
|
||||
echo ""
|
||||
|
||||
# Generate auth secrets if needed
|
||||
if grep -q "your-.*-secret-here" .env 2>/dev/null; then
|
||||
echo -e "${YELLOW}Generating Better-Auth secrets...${NC}"
|
||||
DEV_SECRET=$(openssl rand -base64 32)
|
||||
TEST_SECRET=$(openssl rand -base64 32)
|
||||
PROD_SECRET=$(openssl rand -base64 32)
|
||||
|
||||
sed -i "s/your-dev-secret-here/$DEV_SECRET/" .env
|
||||
sed -i "s/your-test-secret-here/$TEST_SECRET/" .env
|
||||
sed -i "s/your-prod-secret-here/$PROD_SECRET/" .env
|
||||
echo -e "${GREEN}Generated and saved auth secrets${NC}"
|
||||
else
|
||||
echo -e "${GREEN}Auth secrets already configured${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Pre-flight Checklist"
|
||||
echo "======================"
|
||||
echo "Before running docker compose up, ensure:"
|
||||
echo " - DNS records are configured"
|
||||
echo " - Firewall allows ports 80 and 443"
|
||||
echo " - .env file is configured"
|
||||
echo " - Docker images are built (or registry is accessible)"
|
||||
echo ""
|
||||
|
||||
read -p "Do you want to start the stack now? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Starting services..."
|
||||
docker compose up -d
|
||||
echo ""
|
||||
echo -e "${GREEN}Stack is starting!${NC}"
|
||||
echo ""
|
||||
echo "View logs with: docker compose logs -f"
|
||||
echo "SSL certificates may take a few minutes to provision"
|
||||
else
|
||||
echo ""
|
||||
echo "Setup complete! Run 'docker compose up -d' when ready."
|
||||
fi
|
||||
SETUP
|
||||
chmod +x setup.sh
|
||||
echo " setup.sh"
|
||||
|
||||
# ============================================================
|
||||
# Done
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "All files created. Directory listing:"
|
||||
find . -not -path './.git/*' -not -path './.git' | sort
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. cp .env.example .env"
|
||||
echo " 2. nano .env (fill in your secrets)"
|
||||
echo " 3. git add . && git commit -m 'Initial infrastructure setup' && git push -u origin main"
|
||||
175
docker-compose.yml
Normal file
175
docker-compose.yml
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Traefik Reverse Proxy
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- web
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- CF_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./traefik/traefik.yml:/traefik.yml:ro
|
||||
- ./traefik/acme.json:/acme.json
|
||||
- ./traefik/config:/config:ro
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN}`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.middlewares=auth"
|
||||
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$8evjzm8w$$FU3G.qPxZRGlnUKzPJ6QB/"
|
||||
|
||||
# PostgreSQL
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: postgres
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_MULTIPLE_DATABASES: vorteq_dev,vorteq_test,vorteq_prod
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Redis
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Development Environment
|
||||
app-dev:
|
||||
image: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq:dev
|
||||
container_name: vorteq-dev
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/vorteq_dev
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
BETTER_AUTH_SECRET: ${DEV_AUTH_SECRET}
|
||||
BETTER_AUTH_URL: https://${DEV_DOMAIN}
|
||||
PORT: 3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.app-dev.rule=Host(`${DEV_DOMAIN}`)"
|
||||
- "traefik.http.routers.app-dev.entrypoints=websecure"
|
||||
- "traefik.http.routers.app-dev.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.app-dev.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.app-dev.middlewares=dev-headers"
|
||||
- "traefik.http.middlewares.dev-headers.headers.customresponseheaders.X-Environment=development"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
# Testing Environment
|
||||
app-test:
|
||||
image: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq:test
|
||||
container_name: vorteq-test
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
NODE_ENV: test
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/vorteq_test
|
||||
REDIS_URL: redis://redis:6379/1
|
||||
BETTER_AUTH_SECRET: ${TEST_AUTH_SECRET}
|
||||
BETTER_AUTH_URL: https://${TEST_DOMAIN}
|
||||
PORT: 3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.app-test.rule=Host(`${TEST_DOMAIN}`)"
|
||||
- "traefik.http.routers.app-test.entrypoints=websecure"
|
||||
- "traefik.http.routers.app-test.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.app-test.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.app-test.middlewares=test-headers"
|
||||
- "traefik.http.middlewares.test-headers.headers.customresponseheaders.X-Environment=testing"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
# Production Environment
|
||||
app-prod:
|
||||
image: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq:latest
|
||||
container_name: vorteq-prod
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/vorteq_prod
|
||||
REDIS_URL: redis://redis:6379/2
|
||||
BETTER_AUTH_SECRET: ${PROD_AUTH_SECRET}
|
||||
BETTER_AUTH_URL: https://${PROD_DOMAIN}
|
||||
PORT: 3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.app-prod.rule=Host(`${PROD_DOMAIN}`)"
|
||||
- "traefik.http.routers.app-prod.entrypoints=websecure"
|
||||
- "traefik.http.routers.app-prod.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.app-prod.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.app-prod.middlewares=prod-chain"
|
||||
- "traefik.http.middlewares.prod-chain.chain.middlewares=security-headers,rate-limit,compress"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsPreload=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.forceSTSHeader=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
|
||||
- "traefik.http.middlewares.compress.compress=true"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
15
init-databases.sh
Executable file
15
init-databases.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE DATABASE vorteq_dev;
|
||||
GRANT ALL PRIVILEGES ON DATABASE vorteq_dev TO postgres;
|
||||
|
||||
CREATE DATABASE vorteq_test;
|
||||
GRANT ALL PRIVILEGES ON DATABASE vorteq_test TO postgres;
|
||||
|
||||
CREATE DATABASE vorteq_prod;
|
||||
GRANT ALL PRIVILEGES ON DATABASE vorteq_prod TO postgres;
|
||||
EOSQL
|
||||
|
||||
echo "Multiple databases created successfully!"
|
||||
97
setup.sh
Executable file
97
setup.sh
Executable file
|
|
@ -0,0 +1,97 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Setting up Traefik Multi-Environment Stack"
|
||||
echo "=============================================="
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo "Checking prerequisites..."
|
||||
|
||||
command -v docker >/dev/null 2>&1 || {
|
||||
echo -e "${RED}Docker is not installed${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v docker compose >/dev/null 2>&1 || {
|
||||
echo -e "${RED}Docker Compose is not installed${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo -e "${GREEN}Docker and Docker Compose are installed${NC}"
|
||||
|
||||
echo "Creating directory structure..."
|
||||
mkdir -p traefik/{config,logs}
|
||||
|
||||
echo "Setting up SSL certificate storage..."
|
||||
touch traefik/acme.json
|
||||
chmod 600 traefik/acme.json
|
||||
|
||||
chmod +x init-databases.sh
|
||||
|
||||
echo "Creating Docker network..."
|
||||
docker network create web 2>/dev/null || echo -e "${YELLOW}Network 'web' already exists${NC}"
|
||||
|
||||
if [ ! -f .env ]; then
|
||||
echo "Creating .env file..."
|
||||
cp .env.example .env
|
||||
echo -e "${YELLOW}Please edit .env file with your actual values!${NC}"
|
||||
else
|
||||
echo -e "${GREEN}.env file already exists${NC}"
|
||||
fi
|
||||
|
||||
SERVER_IP=$(curl -s ifconfig.me 2>/dev/null || echo "YOUR_SERVER_IP")
|
||||
echo ""
|
||||
echo "Cloudflare DNS Configuration Check"
|
||||
echo "===================================="
|
||||
echo "Make sure these DNS records are configured in Cloudflare:"
|
||||
echo " - quest.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo " - dev.quest.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo " - testing.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo " - traefik.vorteq.wulf.cloud -> ${SERVER_IP}"
|
||||
echo ""
|
||||
echo "Important: Set DNS records to 'DNS only' (grey cloud)"
|
||||
echo ""
|
||||
|
||||
# Generate auth secrets if needed
|
||||
if grep -q "your-.*-secret-here" .env 2>/dev/null; then
|
||||
echo -e "${YELLOW}Generating Better-Auth secrets...${NC}"
|
||||
DEV_SECRET=$(openssl rand -base64 32)
|
||||
TEST_SECRET=$(openssl rand -base64 32)
|
||||
PROD_SECRET=$(openssl rand -base64 32)
|
||||
|
||||
sed -i "s/your-dev-secret-here/$DEV_SECRET/" .env
|
||||
sed -i "s/your-test-secret-here/$TEST_SECRET/" .env
|
||||
sed -i "s/your-prod-secret-here/$PROD_SECRET/" .env
|
||||
echo -e "${GREEN}Generated and saved auth secrets${NC}"
|
||||
else
|
||||
echo -e "${GREEN}Auth secrets already configured${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Pre-flight Checklist"
|
||||
echo "======================"
|
||||
echo "Before running docker compose up, ensure:"
|
||||
echo " - DNS records are configured"
|
||||
echo " - Firewall allows ports 80 and 443"
|
||||
echo " - .env file is configured"
|
||||
echo " - Docker images are built (or registry is accessible)"
|
||||
echo ""
|
||||
|
||||
read -p "Do you want to start the stack now? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Starting services..."
|
||||
docker compose up -d
|
||||
echo ""
|
||||
echo -e "${GREEN}Stack is starting!${NC}"
|
||||
echo ""
|
||||
echo "View logs with: docker compose logs -f"
|
||||
echo "SSL certificates may take a few minutes to provision"
|
||||
else
|
||||
echo ""
|
||||
echo "Setup complete! Run 'docker compose up -d' when ready."
|
||||
fi
|
||||
42
traefik/traefik.yml
Normal file
42
traefik/traefik.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
api:
|
||||
dashboard: true
|
||||
insecure: false
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: ":443"
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
exposedByDefault: false
|
||||
network: web
|
||||
file:
|
||||
directory: /config
|
||||
watch: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: lorentz@wulfconsulting.com
|
||||
storage: acme.json
|
||||
dnsChallenge:
|
||||
provider: cloudflare
|
||||
delayBeforeCheck: 0
|
||||
resolvers:
|
||||
- "1.1.1.1:53"
|
||||
- "8.8.8.8:53"
|
||||
|
||||
log:
|
||||
level: INFO
|
||||
|
||||
accessLog:
|
||||
filePath: /var/log/traefik/access.log
|
||||
bufferingSize: 100
|
||||
Loading…
Add table
Add a link
Reference in a new issue