Self-hosted • Privacy-first • No tracking
Home / Self-Hosted AI / Private Diffusion: A Step-by-Step Guide to Self-Hosting Stable Diffusion with Docker Compose
Self-Hosted AI #privacy#self-hosted#docker-compose#stable-diffusion#ai-image-generation ⏱ 13 min • 👁 2 • Sep 01, 2026

Private Diffusion: A Step-by-Step Guide to Self-Hosting Stable Diffusion with Docker Compose

Learn to deploy Stable Diffusion WebUI privately using Docker Compose. Covers requirements, full configs, security hardening, common errors, and FAQ.

AdSense — Top (970x90) • Responsive
Private Diffusion: A Step-by-Step Guide to Self-Hosting Stable Diffusion with Docker Compose

Introduction

Stable Diffusion has become the de facto standard for local image generation, but for homelab enthusiasts and privacy-conscious users, running it as a cloud service defeats the purpose. Every prompt you send to an online API is logged, analyzed, and potentially used to train models. When you self-host, the entire pipeline—from model weights to your prompt history—stays on your hardware. This guide walks you through deploying AUTOMATIC1111's Stable Diffusion WebUI (the most feature-complete interface) via Docker Compose, with a focus on reproducibility and privacy.

You will learn how to set up a production-ready instance with persistent storage, GPU passthrough, and secure reverse proxy configuration. We will cover the exact environment variables needed, how to avoid common pitfalls like GPU out-of-memory errors and permission mismatches, and how to harden the container without breaking it. By the end, you will have a private, self-hosted image generation service accessible only to you and your family.

This guide assumes you are comfortable with the Linux command line and have basic Docker knowledge. We will not cover installing Docker itself; instead, we focus on the Compose stack and its configuration. All commands are provided in full, copy-paste ready blocks. The version pinning strategy uses environment variables so you can update images without editing the YAML file directly.

A critical note on privacy: self-hosting does not automatically make you anonymous. Your ISP can see traffic to model repositories, and your logs may reveal usage patterns. For true privacy, combine this guide with a VPN for outbound model downloads and consider logging only to stdout (which we configure below).

Prerequisites

Before you begin, ensure your hardware and software meet the following minimum requirements. These are not hard limits but typical baselines for a responsive WebUI with the standard 1.5 or XL models.

Component Minimum (Typical) Recommended (Estimated) Notes
CPU 4 cores, x86_64 8+ cores ARM (Apple Silicon) works with significant performance penalties for some operators.
RAM 16 GB 32 GB The WebUI loads models into RAM; peak usage is typically 2-3x the model size.
GPU NVIDIA GTX 1060 6GB VRAM NVIDIA RTX 3060 12GB+ AMD via ROCm is possible but not covered here. Integrated GPUs are not supported.
Storage 20 GB free 100+ GB Models range from 2 GB (SD 1.5) to 7 GB (SDXL). Add space for generated images and LoRAs.
Software Docker 24+, Docker Compose v2 Latest stable Install from official Docker repos. Do not use distro packages older than 2023.
OS Ubuntu 22.04 LTS or Debian 12 Any systemd-based distro Windows via WSL2 works but requires extra GPU passthrough steps.

You must also have a user account with sudo privileges. Verify your user ID and group ID early; we will need them for the container's user mapping. The default user in the AUTOMATIC1111 image is 1000:1000, but you should confirm on your host.

Step-by-Step Installation Guide

Step 1: Create Project Directory and Environment File

Create a dedicated directory for this stack. We will use ~/sd-webui. The .env file will hold all secrets and version pins, keeping them out of the Compose file and away from version control.

mkdir -p ~/sd-webui && cd ~/sd-webui && touch .env

Open .env with your editor and add the following content. Replace the placeholder values with your own. Never commit this file to Git.

# .env file - set your own values
SD_WEBUI_VERSION=v1.9.3
SD_WEBUI_UID=1000
SD_WEBUI_GID=1000
WEBUI_PORT=7860
# Use a strong password for web authentication
WEBUI_AUTH_USER=admin
WEBUI_AUTH_PASS=change_this_password

The version v1.9.3 is an example. Before pinning, check the official GitHub releases page at https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases — the version above may be outdated by now. The image we use is ghcr.io/automatic1111/stable-diffusion-webui:${SD_WEBUI_VERSION}. If you omit the variable, it defaults to latest, which is not recommended for reproducibility.

Step 2: Create the Docker Compose File

Create docker-compose.yml in the same directory. This file defines the service, its volumes, and environment variables. We use the ghcr.io image because it is maintained by the community and includes CUDA support out of the box.

services:
  sd-webui:
    image: ghcr.io/automatic1111/stable-diffusion-webui:${SD_WEBUI_VERSION:-latest}
    container_name: sd-webui
    restart: unless-stopped
    ports:
      - "${WEBUI_PORT:-7860}:7860"
    environment:
      - PUID=${SD_WEBUI_UID:-1000}
      - PGID=${SD_WEBUI_GID:-1000}
      - WEBUI_AUTH=basic
      - WEBUI_AUTH_USER=${WEBUI_AUTH_USER:-admin}
      - WEBUI_AUTH_PASS=${WEBUI_AUTH_PASS:-changeme}
      - COMMANDLINE_ARGS=--xformers --no-half-vae
    volumes:
      - ./models:/app/stable-diffusion-webui/models
      - ./outputs:/app/stable-diffusion-webui/outputs
      - ./config:/app/stable-diffusion-webui/config
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:7860/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 120s

The COMMANDLINE_ARGS we set (--xformers --no-half-vae) are typical for NVIDIA GPUs with less than 16GB VRAM. --xformers reduces memory usage, and --no-half-vae prevents black images on some cards. Adjust these based on your hardware and the official documentation.

Step 3: Start the Container

Pull the image and start the service in detached mode. The first run will download the base model (default is SD 1.5) which is around 2 GB, so this may take several minutes depending on your connection.

cd ~/sd-webui && docker compose up -d

Check the logs to see if the WebUI is starting correctly. Look for the line indicating the local URL.

cd ~/sd-webui && docker compose logs -f sd-webui

Wait until you see Running on local URL: http://0.0.0.0:7860. Do not press Ctrl+C; instead, open another terminal to continue.

Step 4: Verify WebUI Access

Open your browser and navigate to http://your-server-ip:7860. You should see a login prompt because we enabled basic auth. Use the credentials from your .env file. If you see the WebUI, the installation is successful. If not, proceed to the troubleshooting section below.

Step 5: Download Additional Models (Optional)

By default, the container downloads SD 1.5. For SDXL or community models, place them in the ./models/Stable-diffusion directory on your host. The container maps this to /app/stable-diffusion-webui/models/Stable-diffusion. You can use wget or a browser download.

cd ~/sd-webui/models/Stable-diffusion && wget -O my_model.safetensors https://example.com/path/to/model.safetensors

After adding the file, restart the container to refresh the model list.

cd ~/sd-webui && docker compose restart sd-webui

Step 6: Set Up Persistent Output Directory

Your generated images go to ./outputs on the host. This is a bind mount, so the files are directly accessible. The default output structure is outputs/img2img-grids and outputs/txt2img-images. To change the naming or location, edit the config file in ./config after the first run.

Step 7: Configure User Permissions (Critical)

The image runs as a non-root user. We set PUID and PGID in the environment. Verify your host user's IDs and compare them to the container's expected values.

id -u && id -g

If your user ID is not 1000, you must adjust the .env file. Run id -u and id -g on your host and verify against the image's documentation (default user in this image is 1000:1000). If you use a different UID/GID, the container will create files with those IDs, and you may encounter permission errors when accessing the volumes from the host. To fix, change SD_WEBUI_UID and SD_WEBUI_GID in .env, then run docker compose down && docker compose up -d.

Step 8: Enable Automatic Restart and Health Checks

Our Compose file already includes restart: unless-stopped and a healthcheck. The healthcheck uses curl inside the container; if it is not installed, the healthcheck will fail. The image includes curl, but if you switch to a different image, verify this. To test the health status, run:

cd ~/sd-webui && docker inspect --format='{{.State.Health.Status}}' sd-webui

It should return healthy after the startup period.

Step 9: Update the Stack

To update the WebUI, edit the SD_WEBUI_VERSION in .env to a new release tag, then pull and recreate the container.

cd ~/sd-webui && docker compose pull && docker compose up -d

Your models and outputs persist because they are in bind mounts. Config files may be overwritten if the image updates them; back up ./config before major version jumps.

Step 10: Backup Your Data

Back up the ./models, ./outputs, and ./config directories. The simplest method is a cron job with tar. Do not back up the container itself; the bind mounts are your source of truth.

cd ~/sd-webui && tar -czf sd-backup-$(date +%Y%m%d).tar.gz models outputs config

Store this archive on a different drive or remote location. Test your backups periodically.

Advanced Configuration and Hardening

Reverse Proxy with SSL (Caddy)

To access your WebUI securely from the internet, place a reverse proxy in front of it. Caddy is the simplest option because it auto-provisions Let's Encrypt certificates. Create a Caddyfile on the host and run Caddy as a separate container.

# docker-compose.yml (add this service)
  caddy:
    image: caddy:2.8.4
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

In the Caddyfile, set up a reverse proxy to the WebUI container. Replace sd.example.com with your domain.

sd.example.com {
    reverse_proxy sd-webui:7860
}

Then start Caddy.

cd ~/sd-webui && docker compose up -d caddy

Now your WebUI is accessible at https://sd.example.com. Do not expose port 7860 directly to the internet; keep it bound to localhost or a private network.

Optional Hardening

The following settings are for advanced users who understand the trade-offs. They may break the container if copied verbatim because they remove capabilities or make the filesystem read-only. Test in a staging environment first.

  • Read-only root filesystem: Add read_only: true to the service definition. This prevents any writes to the container's layer, but the WebUI writes to /tmp and in-memory caches. You must mount a temporary volume at /tmp.
  • Drop all capabilities: Add cap_drop: [ALL] and cap_add: [DAC_OVERRIDE] (the latter may be needed for writing to bind mounts). This breaks the container if the image requires CHOWN or NET_BIND_SERVICE. Do not use this with the WebUI unless you have thoroughly tested it.
  • No new privileges: Add security_opt: [no-new-privileges:true] to prevent privilege escalation. This is generally safe and should not break the WebUI.

These hardening measures reduce the attack surface but require careful adjustment per application. The WebUI is a complex Python app; read_only will likely cause errors unless you map /tmp and /app/stable-diffusion-webui/logs to a writable volume.

Troubleshooting Common Errors

Error Cause Solution
RuntimeError: CUDA out of memory. The model does not fit in VRAM. Reduce image dimensions or batch size. Add --medvram or --lowvram to COMMANDLINE_ARGS in the Compose file.
PermissionError: [Errno 13] Permission denied: '/app/stable-diffusion-webui/models/...' UID/GID mismatch between host and container. Verify id -u && id -g on host. Update SD_WEBUI_UID and SD_WEBUI_GID in .env and recreate the container.
Error: No such file or directory: './models/Stable-diffusion' The bind mount directory does not exist on host. Create the directories manually: mkdir -p ~/sd-webui/models/Stable-diffusion ~/sd-webui/outputs ~/sd-webui/config.
Connection refused to http://localhost:7860/health Healthcheck fails because curl is missing in the image. Replace the healthcheck with a Python-based check: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:7860/health')"].
NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver The NVIDIA container toolkit is not installed on the host. Install the NVIDIA Container Toolkit from official docs: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html.
ImagePullBackOff on ghcr.io/automatic1111/... The tag does not exist or the registry is down. Check the tag on the GitHub releases page. Use latest temporarily, but pin a verified tag for production.
API rate limit exceeded when downloading models The model host (e.g., HuggingFace) blocks repeated downloads. Use a token or download manually via browser and place the file in the models directory.

Conclusion

You now have a fully functional, self-hosted Stable Diffusion WebUI running behind a reverse proxy with authentication. The stack is reproducible because all configuration lives in .env and docker-compose.yml. Your data is persistent, and updates are as simple as changing a version number and pulling the new image.

Self-hosting AI image generation is not just about saving money; it is about control. You decide which models to run, when to update, and who has access. The privacy benefits are real: no third party sees your prompts or generated images. The trade-off is operational overhead—you must maintain the server, monitor disk space, and apply security patches. But for a homelab enthusiast, that is part of the fun.

Move forward by exploring model merging, LoRA training, and API integration with tools like sdapi. The WebUI exposes a REST API on port 7860, which you can use to automate image generation from scripts. The official documentation at the AUTOMATIC1111 GitHub repo is the best place to start.

FAQ

Q1: Can I run this on a CPU-only server? Yes, but generation will be extremely slow. A typical 512x512 image on a modern 8-core CPU takes 1-3 minutes, compared to 2-5 seconds on an NVIDIA GPU. Remove the deploy section from the Compose file and add --skip-torch-cuda-test to COMMANDLINE_ARGS. Expect higher RAM usage.

Q2: How do I change the default model? Place your .safetensors or .ckpt file in ./models/Stable-diffusion. Then restart the container. In the WebUI, select the model from the dropdown at the top-left of the page. The selection persists in your browser's session.

Q3: Is it safe to expose the WebUI to the internet? Only if you put a reverse proxy with HTTPS and strong authentication in front of it. The WebUI itself has no built-in rate limiting or brute-force protection. Never expose port 7860 directly. Use Caddy or Traefik with basic auth or OAuth2.

Q4: Why do I get black images? This is commonly reported when the --no-half-vae flag is missing. Some NVIDIA drivers and card combinations produce black images due to VAE precision issues. Add --no-half-vae to COMMANDLINE_ARGS and restart. If the problem persists, switch to a different VAE file.

Q5: How do I update the WebUI without losing my settings? Your settings are stored in ./config on the host. Before updating, back up that directory: cp -r config config-backup. Then change SD_WEBUI_VERSION in .env and run docker compose pull && docker compose up -d. If the new version resets settings, restore the backup and report the issue upstream.

AdSense — In-article (responsive)

Related Guides