Article

DIY Deepfake Guide: Swap Faces Cheaply from Home in Minutes

DIY Deepfake Tutorial: Swap Faces Stealthily from Home for Pennies an Hour

Hey folks, if you're into experimenting with AI-generated content and want to try your hand at creating deepfakes without breaking the bank or needing a beastly PC setup, you're in the right spot. We're talking about face-swapping images using open-source tools like ComfyUI and the Reactor node—all doable from your couch with a cloud GPU rental. This guide walks you through a fully DIY process on RunPod, clocking in at just $0.24 per hour on their budget-friendly RTX 2000 Ada GPU. No fancy hardware required, and we'll keep things stealthy by using private pods and local workflows.

We'll cover everything from signing up to running your first swap, including uncensoring for unrestricted use (but hey, use this responsibly—deepfakes can be fun for memes or art, but they come with ethical strings attached). Expect a realistic setup: it might take 20-30 minutes to get rolling, and results depend on your input images. Let's dive in.

How to make deepfakes illustration

Step 1: Create Your RunPod Account

First things first, you need a RunPod account to rent that cheap GPU time. Head over to RunPod's website and sign up—it's free and straightforward. Just enter your email, set a password, and verify. Once you're in the dashboard, add some credits. The minimum is $10, which gets you about 40 hours on the RTX 2000 Ada at $0.24/hour. That's plenty for testing without commitment. Pro tip: Enable two-factor auth for extra stealth, as these pods are your private cloud space.

From the dashboard, you'll see sections for Pods, Storage, and Templates. We're focusing on Pods for this on-demand setup. If you're new, check out RunPod's getting started guide for a quick overview—it's solid for avoiding newbie pitfalls.

Step 2: Deploy a Minimal Pod

Now, let's spin up your pod. In the dashboard, go to the "Pods" section and hit "Deploy." Search for the template: runpod/pytorch:2.8.0-py3.11-cuda12.8.1-cudnn-devel-ubuntu22.04. This one's perfect—it's lightweight, has Python 3.11, CUDA 12.8 for GPU accel, and PyTorch 2.8 ready to go. No bloat, just what we need for ComfyUI.

Key Settings to Highlight:

  • GPU Type: Select RTX 2000 Ada Generation (Secure Cloud). It's the cheapest at $0.24/hour and handles SDXL models + Reactor swaps without sweating.
  • Storage: Opt for a 20-50GB volume (free ephemeral SSD works for quick tests, but add a $0.20/GB Network Volume for persistence if you plan to save models across sessions—costs about $7/month for 100GB).
  • Environment Variables: Leave defaults unless you're tweaking.
  • Auto-Termination: Set to 10-30 minutes of inactivity to save cash—pods pause when idle.
  • Ports: Expose TCP 8888 (for Jupyter) and 3000 (for ComfyUI).

Click "Deploy," and wait 1-2 minutes for it to go "Running." Your pod ID will show up—note it down, like abc123def. Total cost for setup? Under a buck if you terminate smartly. For more on templates, RunPod's PyTorch guide has deets on why this stack rocks for AI workflows.

Step 3: Connect to Your Pod via Jupyter

With the pod running, connect to manage files and run commands. Back in the dashboard, click your pod, then "Connect." Under HTTP Services, hit "Connect to HTTP Service [Port 8888]." This opens JupyterLab in a new tab at a URL like https://abc123def-8888.proxy.runpod.net/lab (replace with your pod ID).

If it asks for a token, grab it from the pod logs or run jupyter server list in the web terminal later. Jupyter's your control center—file browser on the left, notebooks for testing, and a terminal launcher. Upload any test images here via drag-and-drop to /workspace (the main folder). Stealth mode: All this is behind RunPod's proxy, so no public exposure.

Stuck? RunPod's connect docs cover edge cases like token auth.

Step 4: Open a Terminal and Run the One-Liner Install

In Jupyter, click "New" > "Terminal" (or right-click in the file browser and select "Open Terminal"). You're now in a shell—type pwd to confirm you're at /. Navigate to /workspace with:

cd /workspace

Important: Paste This One-Liner Next. It clones ComfyUI, sets up the environment, installs deps, grabs the Reactor node, downloads SDXL models, and preps face restoration. Copy-paste it directly into the terminal and hit Enter. It'll take 5-15 minutes (downloads are the bottleneck—be patient, no interruptions).

cd /workspace && git clone https://github.com/comfyanonymous/ComfyUI.git && cd ComfyUI && apt update && apt install -y build-essential && python -m venv venv && source venv/bin/activate && pip install --upgrade pip && pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu128 && pip install -r requirements.txt && cd custom_nodes && git clone https://github.com/Gourieff/comfyui-reactor-node.git ComfyUI-ReActor && cd ComfyUI-ReActor && pip install insightface==0.7.3 && python install.py && mkdir -p ../../models/insightface/models/antelopev2 && wget -O ../../models/insightface/models/antelopev2/inswapper_128.onnx https://huggingface.co/ezioruan/inswapper_128.onnx/resolve/main/inswapper_128.onnx && cd ../../models/facerestore_models && wget -O codeformer-v0.1.0.pth https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth && cd ../checkpoints && wget -O sd_xl_base_1.0.safetensors https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors && cd ../vae && wget -O sdxl_vae.safetensors https://huggingface.co/stabilityai/sdxl-vae/resolve/main/sdxl_vae.safetensors && cd /workspace/ComfyUI

What This Does (Breakdown):

  • Clones ComfyUI from GitHub.
  • Updates system, installs build tools for compiling Insightface.
  • Creates/activates a virtual env (isolates everything).
  • Installs PyTorch for CUDA 12.8, then ComfyUI's requirements (transformers, etc.).
  • Clones and installs Reactor node, including Insightface for face detection.
  • Downloads: Inswapper ONNX (~250MB) for swapping, CodeFormer (~540MB) for restoring faces, SDXL base (~7GB) for image gen, and VAE (~335MB) for decoding.
  • Ends back in ComfyUI dir.

If it chugs on downloads, that's normal—HF links are reliable but bandwidth-dependent. Verify with ls models/checkpoints (should show SDXL file).

Step 5: Uncensor the Reactor for Full Access

Reactor has a built-in NSFW filter to block adult content, but for unrestricted deepfakes, we'll tweak it. In Jupyter's file browser, navigate to /workspace/ComfyUI/custom_nodes/ComfyUI-ReActor/scripts/reactor_sfw.py.

Right-click the file > "Open With" > "Editor" (or use terminal: nano reactor_sfw.py). Find the function def nsfw_image(...) (around line 10-20). At the very start of that function, add:

def nsfw_image(image, threshold=0.5):
    return False  # Bypass NSFW check
    # Original code below...

Save and close. This forces it to always return False, disabling the filter. Highlight: Do this ethically—it's for technical freedom, not harm. Sources like this Reddit guide confirm it's a simple, reversible hack. No restart needed yet.

Step 6: Launch and Restart ComfyUI If Needed

From the terminal (still in /workspace/ComfyUI), activate the env and start:

source venv/bin/activate
python main.py --listen 0.0.0.0 --port 3000

This fires up the server. Back in RunPod dashboard, connect to Port 3000: URL like https://abc123def-3000.proxy.runpod.net/. You'll see the ComfyUI interface—a clean node graph editor.

If it's already running or glitches (e.g., port conflict), kill it first:

fuser -k 3000/tcp

Then rerun the start command. In the UI, use the menu (top-right) to refresh nodes if Reactor doesn't show. ComfyUI's GitHub has troubleshooting for restarts.

Step 7: Load the Workflow JSON

Now the fun part—import our minimal deepfake workflow. It's a simple chain: Generate an image from a prompt using SDXL, then swap in a face with Reactor.

In ComfyUI, click "Load" (or drag the JSON into the canvas). Here's the complete workflow JSON—copy it as-is:

{
  "id": "minimal-reactor-workflow",
  "revision": 0,
  "last_node_id": 10,
  "last_link_id": 14,
  "nodes": [
    {
      "id": 1,
      "type": "CheckpointLoaderSimple",
      "pos": [100, 100],
      "size": [270, 98],
      "flags": {},
      "order": 0,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {"name": "MODEL", "type": "MODEL", "links": [1]},
        {"name": "CLIP", "type": "CLIP", "links": [2, 3]},
        {"name": "VAE", "type": "VAE", "links": [4]}
      ],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "CheckpointLoaderSimple"},
      "widgets_values": ["sd_xl_base_1.0.safetensors"]
    },
    {
      "id": 2,
      "type": "CLIPTextEncode",
      "pos": [400, 100],
      "size": [400, 200],
      "flags": {},
      "order": 1,
      "mode": 0,
      "inputs": [{"name": "clip", "type": "CLIP", "link": 2}],
      "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [5]}],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "CLIPTextEncode"},
      "widgets_values": ["a beautiful landscape, mountains, river, sunset"]
    },
    {
      "id": 3,
      "type": "CLIPTextEncode",
      "pos": [400, 350],
      "size": [400, 200],
      "flags": {},
      "order": 2,
      "mode": 0,
      "inputs": [{"name": "clip", "type": "CLIP", "link": 3}],
      "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "CLIPTextEncode"},
      "widgets_values": ["blurry, low quality, artifacts"]
    },
    {
      "id": 4,
      "type": "EmptyLatentImage",
      "pos": [100, 250],
      "size": [270, 106],
      "flags": {},
      "order": 3,
      "mode": 0,
      "inputs": [],
      "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "EmptyLatentImage"},
      "widgets_values": [1024, 1024, 1]
    },
    {
      "id": 5,
      "type": "KSampler",
      "pos": [850, 100],
      "size": [270, 262],
      "flags": {},
      "order": 4,
      "mode": 0,
      "inputs": [
        {"name": "model", "type": "MODEL", "link": 1},
        {"name": "positive", "type": "CONDITIONING", "link": 5},
        {"name": "negative", "type": "CONDITIONING", "link": 6},
        {"name": "latent_image", "type": "LATENT", "link": 7}
      ],
      "outputs": [{"name": "LATENT", "type": "LATENT", "links": [8]}],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "KSampler"},
      "widgets_values": [156680208522230, "randomize", 20, 1, "euler", "normal", 1]
    },
    {
      "id": 6,
      "type": "VAEDecode",
      "pos": [1150, 100],
      "size": [147, 46],
      "flags": {},
      "order": 5,
      "mode": 0,
      "inputs": [
        {"name": "samples", "type": "LATENT", "link": 8},
        {"name": "vae", "type": "VAE", "link": 4}
      ],
      "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "VAEDecode"},
      "widgets_values": []
    },
    {
      "id": 7,
      "type": "LoadImage",
      "pos": [100, 400],
      "size": [340, 314],
      "flags": {},
      "order": 6,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {"name": "IMAGE", "type": "IMAGE", "links": [10]},
        {"name": "MASK", "type": "MASK", "links": null}
      ],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "LoadImage"},
      "widgets_values": ["source_face.png", "image"]
    },
    {
      "id": 8,
      "type": "ReActorFaceSwap",
      "pos": [1350, 100],
      "size": [285, 358],
      "flags": {},
      "order": 7,
      "mode": 0,
      "inputs": [
        {"name": "input_image", "type": "IMAGE", "link": 9},
        {"name": "source_image", "type": "IMAGE", "link": 10},
        {"name": "face_model", "type": "FACE_MODEL", "link": null},
        {"name": "face_boost", "type": "FACE_BOOST", "link": null}
      ],
      "outputs": [
        {"name": "SWAPPED_IMAGE", "type": "IMAGE", "links": [11]},
        {"name": "FACE_MODEL", "type": "FACE_MODEL", "links": null},
        {"name": "ORIGINAL_IMAGE", "type": "IMAGE", "links": null}
      ],
      "properties": {"cnr_id": "comfyui-reactor", "ver": "48a3ad27f99f775dcf63e61276e0110d256597ef", "Node name for S&R": "ReActorFaceSwap"},
      "widgets_values": [true, "inswapper_128.onnx", "retinaface_resnet50", "codeformer-v0.1.0.pth", 1, 1, "no", "no", "0", "0", 1]
    },
    {
      "id": 9,
      "type": "SaveImage",
      "pos": [1650, 100],
      "size": [308, 270],
      "flags": {},
      "order": 8,
      "mode": 0,
      "inputs": [{"name": "images", "type": "IMAGE", "link": 11}],
      "outputs": [],
      "properties": {"cnr_id": "comfy-core", "ver": "0.3.41", "Node name for S&R": "SaveImage"},
      "widgets_values": ["ComfyUI"]
    }
  ],
  "links": [
    [1, 1, 0, 5, 0, "MODEL"],
    [2, 1, 1, 2, 0, "CLIP"],
    [3, 1, 1, 3, 0, "CLIP"],
    [4, 1, 2, 6, 1, "VAE"],
    [5, 2, 0, 5, 1, "CONDITIONING"],
    [6, 3, 0, 5, 2, "CONDITIONING"],
    [7, 4, 0, 5, 3, "LATENT"],
    [8, 5, 0, 6, 0, "LATENT"],
    [9, 6, 0, 8, 0, "IMAGE"],
    [10, 7, 0, 8, 1, "IMAGE"],
    [11, 8, 0, 9, 0, "IMAGE"]
  ],
  "groups": [],
  "config": {},
  "extra": {},
  "version": 0.4
}

The graph will load automatically. Tweak positions if needed by dragging nodes.

Step 8: Choose Your Input Image and Prompt

  • Input Image (Source Face): In the "LoadImage" node (ID 7), click the image slot and upload source_face.png (or your file) from Jupyter's /input folder. Use a clear, front-facing photo for best swaps—blurry sources lead to meh results.

  • Prompt: Double-click the "CLIPTextEncode" nodes (IDs 2 and 3). Edit the positive prompt for your base image, e.g., "a person standing in a city street, realistic, high detail." Negative: Keep as-is or add "deformed, ugly" for cleaner gens. This generates the body/scene, then Reactor swaps the face in.

Highlight: Start Simple. Test with neutral prompts to dial in quality—SDXL shines at 1024x1024, but drop to 512x512 in EmptyLatent (ID 4) for faster runs.

Step 9: Run the Workflow

Hit "Queue Prompt" (top-right button). Watch the progress bar—sampling takes 10-20 seconds, Reactor adds 2-5 more. Output saves to /workspace/ComfyUI/output as PNGs. Download via Jupyter.

First run might cache models (extra 30s), but subsequent ones fly. For stealth, terminate the pod after—your files persist if using Network Volume.

Troubleshooting Common Issues

  • Pod Won't Start: Check credits; try a different region (e.g., EU for availability). See RunPod troubleshooting.
  • Download Fails: Rerun the wget parts manually; use --continue flag, e.g., wget -c -O file.pth URL.
  • Reactor Errors: If "No face detected," ensure source image has a clear face. Reinstall Insightface: pip install insightface==0.7.3 --force-reinstall.
  • Out of VRAM: Lower res or steps in KSampler. RTX 2000 Ada handles 1024x1024 fine, but batches >1 might OOM.
  • NSFW Still Blocks: Double-check the edit in reactor_sfw.py; restart ComfyUI.
  • Slow UI: Proxy lag—refresh or use VS Code connect alternative from dashboard.

If stuck, ComfyUI's Discord or Reactor's GitHub issues are goldmines.

Advanced Tips for Better Deepfakes

Want video swaps? Chain with AnimateDiff nodes (install via ComfyUI Manager—search in UI). For blending multiple faces, add "ReActorLoadFaceModel" and save .safetensors. Experiment with Reactor widgets: Crank face restore to 0.8 for smoother blends, or enable masking to exclude hats/eyes.

Cost breakdown: 10 swaps = ~10 minutes = $0.04. Scale up with serverless if needed, but pods keep it DIY.

FAQs

Q: Is this legal?
A: Tools are open-source, but deepfakes involving real people without consent can violate laws (e.g., privacy, defamation). Stick to public figures or fiction for safety—check your local regs.

Q: Can I run this locally?
A: Sure, if you have an NVIDIA GPU with 8GB+ VRAM. Clone the same repos on your machine, but RunPod's cheaper for occasional use.

Q: How do I save models persistently?
A: Mount a Network Volume during deploy—attach to /workspace and your stuff survives pod restarts.

Q: Better models for realism?
A: Swap SDXL for Flux.1 (wget from HF), but it needs more VRAM. See this Flux guide.

There you have it—a realistic path to homebrew deepfakes without the hassle. Play around, but remember: with great power comes great responsibility. Got tweaks or questions? Drop a comment below!