devops

DevOps for Web Development

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "devops" with this command: npx skills add vapvarun/claude-backup/vapvarun-claude-backup-devops

DevOps for Web Development

Containerization, CI/CD, deployment, and infrastructure practices.

Docker

Dockerfile Best Practices

Multi-stage build for Node.js

FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production

FROM node:20-alpine AS runner WORKDIR /app

Run as non-root user

RUN addgroup -g 1001 -S nodejs &&
adduser -S nextjs -u 1001

COPY --from=builder /app/node_modules ./node_modules COPY --chown=nextjs:nodejs . .

USER nextjs EXPOSE 3000 ENV NODE_ENV production CMD ["node", "server.js"]

PHP/Laravel

FROM php:8.2-fpm-alpine AS base

RUN apk add --no-cache
libzip-dev
libpng-dev
oniguruma-dev
&& docker-php-ext-install
pdo_mysql
zip
gd
mbstring
opcache

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

Production stage

FROM base AS production COPY . . RUN composer install --no-dev --optimize-autoloader RUN php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache

Development stage

FROM base AS development RUN apk add --no-cache $PHPIZE_DEPS &&
pecl install xdebug &&
docker-php-ext-enable xdebug

Docker Compose

docker-compose.yml

version: '3.8'

services: app: build: context: . target: development volumes: - .:/var/www/html - /var/www/html/vendor ports: - "8000:8000" environment: - APP_ENV=local - DB_HOST=db - REDIS_HOST=redis depends_on: db: condition: service_healthy redis: condition: service_started

db: image: mysql:8.0 volumes: - db_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: app healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5

redis: image: redis:7-alpine volumes: - redis_data:/data

nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - .:/var/www/html depends_on: - app

volumes: db_data: redis_data:

CI/CD

GitHub Actions

.github/workflows/ci.yml

name: CI/CD Pipeline

on: push: branches: [main, develop] pull_request: branches: [main]

env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}

jobs: test: runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: test ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

steps:
  - uses: actions/checkout@v4

  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'npm'

  - name: Install dependencies
    run: npm ci

  - name: Run linting
    run: npm run lint

  - name: Run tests
    run: npm test -- --coverage
    env:
      DATABASE_URL: mysql://root:secret@localhost:3306/test

  - name: Upload coverage
    uses: codecov/codecov-action@v3
    with:
      token: ${{ secrets.CODECOV_TOKEN }}

build: needs: test runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
  - uses: actions/checkout@v4

  - name: Log in to Container Registry
    uses: docker/login-action@v3
    with:
      registry: ${{ env.REGISTRY }}
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}

  - name: Build and push Docker image
    uses: docker/build-push-action@v5
    with:
      context: .
      push: true
      tags: |
        ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
        ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
      cache-from: type=gha
      cache-to: type=gha,mode=max

deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: production

steps:
  - name: Deploy to server
    uses: appleboy/ssh-action@master
    with:
      host: ${{ secrets.SERVER_HOST }}
      username: ${{ secrets.SERVER_USER }}
      key: ${{ secrets.SSH_PRIVATE_KEY }}
      script: |
        cd /var/www/app
        docker compose pull
        docker compose up -d
        docker system prune -f

GitLab CI

.gitlab-ci.yml

stages:

  • test
  • build
  • deploy

variables: DOCKER_TLS_CERTDIR: "/certs"

test: stage: test image: node:20-alpine services: - mysql:8.0 variables: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: test script: - npm ci - npm run lint - npm test coverage: '/All files[^|]|[^|]\s+([\d.]+)/' artifacts: reports: coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml

build: stage: build image: docker:24 services: - docker:24-dind script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - main

deploy: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | ssh-add - script: - ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_HOST " cd /var/www/app && docker compose pull && docker compose up -d" only: - main environment: name: production url: https://example.com

Nginx Configuration

/etc/nginx/sites-available/app.conf

upstream app { server app:3000; keepalive 64; }

server { listen 80; server_name example.com; return 301 https://$server_name$request_uri; }

server { listen 443 ssl http2; server_name example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

root /var/www/html/public;
index index.html index.php;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;

# Static files
location /static/ {
    alias /var/www/html/static/;
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# API proxy
location /api/ {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
}

# PHP-FPM (Laravel)
location ~ \.php$ {
    fastcgi_pass php:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

}

Monitoring

Health Checks

// Express health check endpoint app.get('/health', async (req, res) => { const checks = { uptime: process.uptime(), timestamp: Date.now(), database: 'unknown', redis: 'unknown', };

try {
    await db.query('SELECT 1');
    checks.database = 'healthy';
} catch (e) {
    checks.database = 'unhealthy';
}

try {
    await redis.ping();
    checks.redis = 'healthy';
} catch (e) {
    checks.redis = 'unhealthy';
}

const isHealthy = checks.database === 'healthy' && checks.redis === 'healthy';

res.status(isHealthy ? 200 : 503).json(checks);

});

Logging

// Winston logger setup import winston from 'winston';

const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'api' }, transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }), new winston.transports.File({ filename: 'logs/error.log', level: 'error', }), new winston.transports.File({ filename: 'logs/combined.log', }), ], });

// Request logging middleware app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { logger.info('Request completed', { method: req.method, url: req.url, status: res.statusCode, duration: Date.now() - start, ip: req.ip, }); }); next(); });

Prometheus Metrics

import client from 'prom-client';

// Enable default metrics client.collectDefaultMetrics();

// Custom metrics const httpRequestDuration = new client.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status'], buckets: [0.1, 0.5, 1, 2, 5], });

// Middleware app.use((req, res, next) => { const end = httpRequestDuration.startTimer(); res.on('finish', () => { end({ method: req.method, route: req.route?.path || 'unknown', status: res.statusCode }); }); next(); });

// Metrics endpoint app.get('/metrics', async (req, res) => { res.set('Content-Type', client.register.contentType); res.end(await client.register.metrics()); });

Server Setup

Ubuntu Server Hardening

#!/bin/bash

Update system

apt update && apt upgrade -y

Create deploy user

adduser deploy usermod -aG sudo deploy usermod -aG docker deploy

SSH hardening

sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config systemctl restart sshd

Firewall

ufw default deny incoming ufw default allow outgoing ufw allow ssh ufw allow http ufw allow https ufw enable

Install Docker

curl -fsSL https://get.docker.com | sh

Install fail2ban

apt install fail2ban -y systemctl enable fail2ban

Automatic security updates

apt install unattended-upgrades -y dpkg-reconfigure -plow unattended-upgrades

Environment Management

.env.production

NODE_ENV=production DATABASE_URL=mysql://user:pass@db:3306/app REDIS_URL=redis://redis:6379 SECRET_KEY=${SECRET_KEY} # From secrets manager

docker-compose.prod.yml

version: '3.8' services: app: image: ghcr.io/org/app:latest env_file: - .env.production secrets: - db_password - api_key deploy: replicas: 3 update_config: parallelism: 1 delay: 10s restart_policy: condition: on-failure

secrets: db_password: external: true api_key: external: true

Backup Strategy

#!/bin/bash

backup.sh

BACKUP_DIR="/backups" DATE=$(date +%Y%m%d_%H%M%S) RETENTION_DAYS=30

Database backup

docker exec db mysqldump -u root -p$MYSQL_ROOT_PASSWORD app | gzip > $BACKUP_DIR/db_$DATE.sql.gz

Files backup

tar -czf $BACKUP_DIR/files_$DATE.tar.gz /var/www/html/uploads

Upload to S3

aws s3 cp $BACKUP_DIR/db_$DATE.sql.gz s3://bucket/backups/db/ aws s3 cp $BACKUP_DIR/files_$DATE.tar.gz s3://bucket/backups/files/

Cleanup old backups

find $BACKUP_DIR -type f -mtime +$RETENTION_DAYS -delete

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

wp-theme-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review