Skip to content
← Back to snippets
BashDockerGitGitHub ActionsNginx

DevOps snippets: Docker, Nginx, Git, GitHub Actions

Sixteen operational snippets: multi-stage Docker builds and non-root containers, Nginx reverse proxy with SSL, load balancing and WebSocket proxying, Git rebase, stash and bisect workflows, and GitHub Actions for CI, deploys and cron.

Everything here runs between "the code is written" and "the code is serving traffic". Four tools, one job: getting a build out and keeping it reachable.

Docker: the image size problem is a build-stage problem

A single-stage Node image ships the compiler, the dev dependencies and the source alongside the thing you actually run. Multi-stage builds solve this by copying only the build output into a clean runtime image, and the difference is usually not marginal — it is most of the image. Smaller images pull faster on every deploy and present a smaller surface to scan.

Running as a non-root user is the other half. Containers do not isolate as strongly as a VM, and a process running as root inside the container is running as a user that maps to root on the host in the default configuration. Adding a user and dropping to it costs two lines. Pairing it with a read-only root filesystem costs one more and removes an entire category of persistence.

Nginx: proxying is easy, WebSockets are the exception

A reverse proxy for HTTP is four directives. WebSockets need two more — Upgrade and Connection — because the protocol switch will not happen without them, and the failure mode is confusing: the handshake returns 200 instead of 101 and the client retries forever. It is the single most common Nginx question for anyone adding real-time features to a working site.

The SSL configuration is where defaults matter most: the Mozilla SSL configuration generator stays current with what is considered safe, and copying a five-year-old cipher list from a blog post is how sites end up failing an audit.

Git: three commands for three different situations

Stash is for interrupted work you will return to in minutes. Interactive rebase is for tidying a branch before review — squashing the "fix typo" commits so the history describes intent rather than the order things occurred to you. Bisect is for the question "when did this break", and it is dramatically faster than reading diffs: it finds the offending commit in a handful of steps regardless of how many commits are in range.

GitHub Actions: the deploy is the part with secrets in it

The CI workflow is mostly plumbing. The deploy workflow is where care is required — SSH keys belong in encrypted secrets, never in the repository, and a scheduled workflow needs to be idempotent because GitHub does not guarantee exact timing and will occasionally skip or delay a run.

Docker

Docker Compose for Node.js + PostgreSQL + Redis

A production-style docker-compose.yml for a Node.js app with PostgreSQL and Redis — the standard SaaS backend stack.

How it works: three services — app (Node.js), db (PostgreSQL 16), and cache (Redis 7) — on a shared network. The app waits for both to be healthy before starting. Volumes keep data across restarts, and .env is loaded automatically.

services:
  app:
    build: .
    ports:
      - "3000:3000"
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
      POSTGRES_DB: ${DB_NAME}
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:
# Start all services
docker compose up -d

# Follow logs
docker compose logs -f app

# Tear down (keeps volumes)
docker compose down

# Tear down completely (removes data)
docker compose down -v
Docker

Docker multi-stage builds for Node.js

A production Dockerfile with multi-stage builds — separate deps, build, and runtime stages for a minimal final image.

How it works: three stages. deps installs dependencies, builder compiles the app, and runner copies only what's needed into a slim image. No dev dependencies, no source code — just the compiled output and production node_modules.

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile

# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable pnpm && pnpm build

# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 appuser
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
COPY --from=builder --chown=appuser:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:nodejs /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
docker build -t myapp:latest .
docker images --filter reference=myapp
# Final image is ~150MB instead of ~1.2GB
Docker

Docker security — non-root user and read-only filesystem

Harden your containers by running as a non-root user with a read-only root filesystem and dropped kernel capabilities.

How it works: create an unprivileged user inside the image, switch to it with USER, then pass security options at runtime — --read-only prevents writes to the container filesystem, --cap-drop=ALL removes all kernel capabilities, and --security-opt=no-new-privileges prevents privilege escalation.

FROM node:20-alpine
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
# Run with security hardening
docker run --rm \
  --read-only \
  --cap-drop=ALL \
  --security-opt=no-new-privileges:true \
  --tmpfs /tmp \
  -p 3000:3000 \
  myapp:latest

# docker-compose equivalent
# security_opt:
#   - no-new-privileges:true
# cap_drop:
#   - ALL
# read_only: true
# tmpfs:
#   - /tmp
NginxNginx

Nginx load balancing between Node.js instances

Distribute traffic across multiple Node.js backend instances with round-robin, health checks, and passive failover — no application changes needed.

How it works: the upstream block defines a group of backend servers. Nginx distributes requests using round-robin by default. If a server fails to respond, nginx marks it as unavailable and routes traffic to the remaining healthy servers.

upstream node_backend {
    # Round-robin load balancing (default)
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;

    # Optional: least-connected distribution
    # least_conn;

    # Optional: IP-based sticky sessions
    # ip_hash;

    # Mark server as down for maintenance
    # server 127.0.0.1:3004 down;

    # Passive health check
    # After 3 failures, mark unavailable for 30s
    server 127.0.0.1:3005 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://node_backend;
        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;

        # Timeouts
        proxy_connect_timeout 5s;
        proxy_read_timeout 10s;
        proxy_send_timeout 10s;
    }
}
# Scale Node.js instances with PM2
pm2 start server.js -i 3
NginxNginx

Nginx reverse proxy with SSL termination

A hardened nginx configuration for proxying Node.js apps — SSL termination, rate limiting, security headers, and gzip.

How it works: nginx terminates TLS at the edge, forwards requests to a local Node.js backend, and adds security headers, rate limiting, and gzip compression — all before a single line of app code runs.

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

    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Rate limiting
    limit_req zone=api burst=20 nodelay;
    limit_req_status 429;

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

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}
# Test config before reload
sudo nginx -t

# Reload gracefully
sudo nginx -s reload
NginxNginx

Nginx static file server with caching

Serve static assets (CSS, JS, images) with far-future cache headers, gzip compression, and directory index — no app server needed.

How it works: nginx serves files from /var/www/html directly — no proxy. Cache headers tell the browser to store assets for a year; files with content hashes in their name are safe to cache forever. Gzip reduces transfer size, and try_files enables SPA routing.

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.html;

    # Gzip
    gzip on;
    gzip_types text/plain text/css application/json application/javascript image/svg+xml;
    gzip_min_length 256;

    # Far-future cache for versioned assets
    location ~* \.(?:css|js|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Cache images for a week
    location ~* \.(?:jpg|jpeg|png|webp|avif)$ {
        expires 7d;
        add_header Cache-Control "public";
    }

    # No cache for HTML
    location ~* \.html?$ {
        expires -1;
        add_header Cache-Control "no-cache";
    }

    # SPA fallback — serve index.html for non-file routes
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
    }
}
NginxNginx

Nginx WebSocket proxy for real-time apps

Proxy WebSocket connections through nginx — required for Socket.io, live chat, and collaborative editing behind a reverse proxy.

How it works: WebSocket connections start as HTTP upgrades. The Upgrade and Connection headers must be forwarded explicitly. Without this configuration, nginx closes the connection after 60s. The proxy_read_timeout of 1h keeps long-lived WebSocket connections alive.

server {
    listen 80;
    server_name app.example.com;

    location /ws {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # WebSocket upgrade headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Standard proxy headers
        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;

        # Long timeout for persistent connections
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # Regular HTTP traffic
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
# Test WebSocket connection
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" http://app.example.com/ws
GitGit

Git cherry-pick and bisect — target specific commits

Cherry-pick applies a specific commit from any branch to your current branch. Bisect performs a binary search through history to find the commit that introduced a bug.

How it works: cherry-pick takes the diff from a single commit and applies it to HEAD — no merge, no rebase. bisect marks commits as good or bad, and Git narrows down the exact commit where things broke, usually in under 10 steps even for thousands of commits.

# Cherry-pick a single commit
git cherry-pick a1b2c3d

# Cherry-pick with auto-commit message
git cherry-pick --no-commit a1b2c3d

# Cherry-pick a range of commits
git cherry-pick a1b2c3d..e5f6g7h

# Cherry-pick from another branch
git checkout main
git cherry-pick feature-branch~3..feature-branch

# Start bisect session
git bisect start
git bisect bad          # current commit is broken
git bisect good v2.0.0  # last known good tag

# Git checks out the midpoint — test it, then:
git bisect good   # or git bisect bad
# Repeat until Git identifies the first bad commit

# End bisect session
git bisect reset

# See what changed in a specific commit
git show a1b2c3d
GitGit

Git log — filtering and formatting commit history

Search, filter, and format your Git log — find commits by author, date, file, or content with a readable one-line output.

How it works: git log accepts dozens of filtering flags. Combine them to find exactly the commits you need — who changed a file, when, and why — without scrolling through irrelevant history.

# One line per commit
git log --oneline

# Graph view with branches
git log --oneline --graph --all

# Commits by author
git log --author="Alex"

# Commits since a date
git log --since="2026-06-01"
git log --after="2 weeks ago"

# Commits that modified a specific file
git log --oneline -- src/server.ts

# Search commits by message
git log --grep="fix:" --oneline

# Search commits that added/removed a function
git log -S "calculateTotal" --oneline

# Commits in a date range
git log --since="2026-06-01" --until="2026-07-01"

# Custom format
git log --pretty=format:"%h %an %ar %s"

# Show diff stats
git log --stat --oneline

# Last 5 commits
git log -5 --oneline

# Commits in branch not in main
git log main..feature --oneline
GitGit

Git squash commits and interactive rebase

Clean up your Git history with squash, fixup, and interactive rebase — keep a readable commit log before merging.

How it works: interactive rebase (git rebase -i) opens an editor where you can squash, fixup, reword, or reorder commits. Squash combines a commit into the previous one and lets you edit the message; fixup does the same but discards the message — perfect for "fix typo" and "wip" commits.

# Squash the last 3 commits into one
git rebase -i HEAD~3

# Editor opens with:
# pick a1b2c3d Add login page
# pick e4f5g6h Fix typo on login page
# pick i7j8k9l Add tests for login

# Change to:
# pick a1b2c3d Add login page
# squash e4f5g6h Fix typo on login page
# fixup i7j8k9l Add tests for login
# Save and close → one clean commit
# Squash all commits on a feature branch into one
git checkout feature/my-feature
git rebase -i main

# Mark all commits except the first as "squash"
# or use the one-liner:
git reset --soft main
git commit -m "feat: add my feature"
# Amend the last commit (if not pushed yet)
git add . && git commit --amend --no-edit

# Force push after rebase (only on feature branches!)
git push --force-with-lease
GitGit

Git stash — save and restore work in progress

Temporarily save uncommitted changes with git stash — switch branches, pull updates, and restore your work later without losing anything.

How it works: git stash takes all tracked-but-uncommitted changes and pushes them onto a stack. Your working tree becomes clean so you can switch branches. git stash pop restores the latest stash and removes it from the stack.

# Save current work (tracked files only)
git stash

# Save including untracked files
git stash -u

# Save with a descriptive message
git stash push -m "WIP: refactoring auth middleware"

# List all stashes
git stash list
# stash@{0}: On feature/login: WIP: refactoring auth middleware
# stash@{1}: WIP: fixing header styles

# Restore the latest stash
git stash pop

# Restore a specific stash
git stash pop stash@{1}

# Apply stash without removing it from the stack
git stash apply stash@{0}

# Create a branch from a stash (handles conflicts)
git stash branch feature/new-branch stash@{0}

# Drop a specific stash
git stash drop stash@{1}

# Clear all stashes
git stash clear
GitHub Actions

GitHub Actions — CI pipeline for PRs

Run linting, type-checking, and tests on every pull request before merge — fail fast with clear annotations on the PR.

How it works: triggered on pull_request to main, the workflow sets up Node.js, installs deps with caching, runs lint, type-check, and tests in parallel, then uploads test results as annotations on the PR.

name: CI

on:
  pull_request:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test -- --coverage
      - uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            src:
              - 'src/**'
      - name: Upload coverage
        if: steps.changes.outputs.src == 'true'
        uses: codecov/codecov-action@v4
# Required branch protection rules:
# Require status check "quality" before merge
GitHub Actions

GitHub Actions — deploy Node.js app to VPS via SSH

A CI/CD pipeline that lints, tests, builds, and deploys a Node.js app to a VPS over SSH with zero-downtime.

How it works: on every push to main, the workflow checks out the code, installs dependencies, runs linting and tests, builds the project, syncs the build to the VPS via rsync over SSH, and performs a zero-downtime reload with PM2.

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci
      - run: npm run lint
      - run: npm test
      - run: npm run build

      - name: Deploy via rsync
        uses: easingthemes/ssh-deploy@v4
        with:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
          REMOTE_USER: ${{ secrets.REMOTE_USER }}
          SOURCE: .output/
          TARGET: /var/www/app/
          ARGS: -rlpDzvc --delete

      - name: Reload app
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/app
            pm2 reload ecosystem.config.js
# Required secrets for the workflow:
# SSH_PRIVATE_KEY — your deploy key
# REMOTE_HOST — VPS IP or domain
# REMOTE_USER — SSH user
GitHub Actions

GitHub Actions — build and push Docker image to registry

Automatically build and push a Docker image to Docker Hub or GitHub Container Registry on every push to main — with layer caching.

How it works: on push to main, the workflow logs into the container registry, sets up Docker Buildx for multi-platform builds, uses GitHub Actions cache for layers, builds the image with version tags, and pushes it. Pull requests get an image built but not pushed.

name: Docker Build & Push

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

jobs:
  docker:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=ref,event=branch
            type=sha,format=short

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
GitHub Actions

GitHub Actions — scheduled tasks with cron

Run workflows on a schedule — daily backups, weekly dependency updates, or hourly health checks — using cron syntax.

How it works: the schedule event uses POSIX cron syntax. The workflow runs at the specified intervals regardless of pushes. Use workflow_dispatch to also trigger it manually for testing.

name: Daily Backup

on:
  schedule:
    # Every day at 2:00 AM UTC
    - cron: '0 2 * * *'
  workflow_dispatch:

jobs:
  backup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Create database dump
        run: |
          pg_dump $DATABASE_URL > backup_$(date +%Y-%m-%d).sql
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

      - name: Upload to S3-compatible storage
        uses: jakejarvis/s3-sync-action@v1
        with:
          args: --acl private
        env:
          AWS_S3_BUCKET: ${{ secrets.AWS_BACKUP_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          SOURCE_DIR: .
# Common cron schedules:
# Every hour:      0 * * * *
# Daily at midnight: 0 0 * * *
# Weekly on Monday:  0 0 * * 1
# Monthly on 1st:    0 0 1 * *
# Every 15 minutes: */15 * * * *
BashBash

yt-dlp — Handy Bash Functions for Video, Audio & Subtitles

A set of functions for .bashrc / .zshrc to quickly download videos (up to 1080p), export MP3 audio, extract clean subtitles without timestamps, and save thumbnails.

A ready-to-use set of shell functions to append to your ~/.bashrc or ~/.zshrc. Utilizes Firefox cookies, Node.js runtime, and bypasses common download throttling with yt-dlp.

Function Reference & Mnemonic Trick:

The dlp prefix can be remembered as short for DownLoad Please (or yt-dlp), while the suffix specifies the output format:

  • dlpv URL — DownLoad Please Video — download video capped at 1080p (MP4 + M4A).
  • dlpa URL — DownLoad Please Audio — extract and convert audio stream to MP3.
  • dlps URL — DownLoad Please Subtitles — download clean plain-text subtitles without the video.
  • dlpvs URL — DownLoad Please Video + Subtitles — download video with cleaned English subtitles.
  • dlpvt URL — DownLoad Please Video + Thumbnail — download video along with its original thumbnail.
# video
dlpv() {
    yt-dlp --cookies-from-browser Firefox --js-runtime node --remote-components ejs:github -f "bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" "$1"
}

# audio
dlpa() {
    yt-dlp --cookies-from-browser Firefox --js-runtime node --remote-components ejs:github -x --audio-format mp3 -o "%(title)s [%(id)s].%(ext)s" "$1"
}

# video & subtitles
dlpvs() {
    yt-dlp --cookies-from-browser Firefox --js-runtime node --remote-components ejs:github --write-subs --write-auto-subs --sub-lang en --sub-format ttml --convert-subs txt --exec before_dl:"sed -e '/^[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]$/d' -e '/^[[:digit:]]\{1,3\}$/d' -e 's/<[^>]*>//g' -e '/^[[:space:]]*$/d' -i '' %(requested_subtitles.:.filepath)#q" -f "bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" "$1"
}

# subtitles
dlps() {
    yt-dlp --cookies-from-browser Firefox --js-runtime node --remote-components ejs:github --skip-download --write-subs --write-auto-subs --sub-lang en --sub-format ttml --convert-subs txt --exec before_dl:"sed -e '/^[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]$/d' -e '/^[[:digit:]]\{1,3\}$/d' -e 's/<[^>]*>//g' -e '/^[[:space:]]*$/d' -i '' %(requested_subtitles.:.filepath)#q" "$1"
}

# video & thumbnail
dlpvt() {
    yt-dlp --cookies-from-browser Firefox --js-runtime node --remote-components ejs:github --write-thumbnail -f "bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" "$1"
}
// CONNECT

Contact Information

Submit a request or write to me directly on Telegram. I'll reply within 24 hours.

Provide either your email or Telegram so I can reply.