Home Office

Replace Google Workspace, ChatGPT, and Google Search with a self-hosted office stack. Files, notes, AI assistant, private search, and news — all on your hardware, all yours.


Goal

Create a home office that:

  • Syncs files, calendar, and contacts across all devices (replacing Google Workspace)
  • Provides a local AI assistant for writing, research, and coding (replacing ChatGPT)
  • Searches the web privately without tracking (replacing Google Search)
  • Aggregates news and articles without algorithms (replacing Google News)
  • Saves articles for later reading without ads (replacing Pocket)
  • Manages passwords securely (replacing LastPass)
  • All runs locally — your work stays on your machine

Components

Component Catalog Card Role
Debian compute/os Base operating system. Ubuntu Server also works.
Docker compute/container Container runtime
Nextcloud applications/cloud Files, calendar, contacts, office docs
Joplin applications/notes Markdown notes with E2E encryption
Ollama compute/inference Local LLM runtime
SearXNG applications/search Private metasearch engine
FreshRSS applications/news RSS feed reader
Wallabag applications/reading Read-it-later service
Vaultwarden security/passwords Password manager
Caddy network/proxy Reverse proxy with HTTPS
PostgreSQL storage/database Database for Nextcloud

All are A3/T2.


Running Proxmox? Create a VM (Debian, 4+ GB RAM), install Docker inside it, then follow this recipe. Benefit: VM-level snapshots before updates, isolation from other stacks, easy backup via vzdump. See Proxmox VE.

Who is this for?

  • Remote workers who want control over their data
  • Freelancers who don’t want to pay for Google Workspace
  • Anyone who uses ChatGPT daily and wants a private alternative
  • Developers who want local AI for code assistance
  • Privacy-conscious professionals

Estimated setup time: 2–3 hours. Minimum hardware: 8 GB RAM minimum. 16 GB recommended if running Ollama with larger models. GPU optional but significantly improves LLM speed.


Step-by-Step Instructions

1. Create Project Directory

mkdir -p /opt/home-office && cd /opt/home-office

2. Create Docker Compose

cat > docker-compose.yml << 'YAMLEOF'
services:

  # ── Reverse Proxy ──────────────────────────────────
  caddy:
    image: caddy:latest
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./config/caddy/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    restart: unless-stopped

  # ── Database ───────────────────────────────────────
  postgres:
    image: postgres:16
    container_name: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 5s
      retries: 3

  # ── Files + Calendar + Contacts ────────────────────
  nextcloud:
    image: nextcloud:stable
    container_name: nextcloud
    environment:
      POSTGRES_HOST: postgres
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
      NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
    volumes:
      - ./data/nextcloud:/var/www/html
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  # ── Notes ──────────────────────────────────────────
  joplin:
    image: joplin/server:latest
    container_name: joplin
    ports:
      - "22300:22300"
    environment:
      APP_BASE_URL: https://notes.${DOMAIN}
      DB_CLIENT: pg
      POSTGRES_HOST: postgres
      POSTGRES_PORT: 5432
      POSTGRES_DATABASE: joplin
      POSTGRES_USER: joplin
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  # ── AI Assistant ───────────────────────────────────
  ollama:
    image: ollama/ollama
    container_name: ollama
    ports:
      - "11434:11434"
    volumes:
      - ./data/ollama:/root/.ollama
    restart: unless-stopped

  # ── Private Search ─────────────────────────────────
  searxng:
    image: searxng/searxng
    container_name: searxng
    ports:
      - "8888:8080"
    volumes:
      - ./data/searxng:/etc/searxng
    environment:
      - SEARXNG_BASE_URL=https://search.${DOMAIN}
    restart: unless-stopped

  # ── RSS Reader ─────────────────────────────────────
  freshrss:
    image: freshrss/freshrss
    container_name: freshrss
    ports:
      - "8082:80"
    volumes:
      - ./data/freshrss/data:/var/www/FreshRSS/data
      - ./data/freshrss/extensions:/var/www/FreshRSS/extensions
    environment:
      - TZ=UTC
      - CRON_MIN=2,32
    restart: unless-stopped

  # ── Read Later ─────────────────────────────────────
  wallabag:
    image: wallabag/wallabag
    container_name: wallabag
    ports:
      - "8090:80"
    volumes:
      - ./data/wallabag/images:/var/www/wallabag/web/assets/images
      - ./data/wallabag/data:/var/www/wallabag/data
    environment:
      - SYMFONY__ENV__DOMAIN_NAME=https://read.${DOMAIN}
      - SYMFONY__ENV__DATABASE_DRIVER=pdo_sqlite
    restart: unless-stopped

  # ── Passwords ──────────────────────────────────────
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    environment:
      DOMAIN: https://vault.${DOMAIN}
      ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN}
    volumes:
      - ./data/vaultwarden:/data
    restart: unless-stopped

volumes:
  caddy_data:
YAMLEOF

3. Create Environment File

cat > .env << 'EOF'
DOMAIN=office.local
POSTGRES_PASSWORD=CHANGE_ME_1
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=CHANGE_ME_2
VAULTWARDEN_ADMIN_TOKEN=CHANGE_ME_3
EOF

4. Configure Caddy

mkdir -p config/caddy
cat > config/caddy/Caddyfile << 'EOF'
cloud.{$DOMAIN} {
    reverse_proxy nextcloud:80
}
notes.{$DOMAIN} {
    reverse_proxy joplin:22300
}
search.{$DOMAIN} {
    reverse_proxy searxng:8080
}
news.{$DOMAIN} {
    reverse_proxy freshrss:80
}
read.{$DOMAIN} {
    reverse_proxy wallabag:80
}
vault.{$DOMAIN} {
    reverse_proxy vaultwarden:80
}
ai.{$DOMAIN} {
    reverse_proxy ollama:11434
}
EOF

5. Initialize PostgreSQL Databases

mkdir -p config/postgres
cat > config/postgres/init-databases.sql << 'EOF'
CREATE USER nextcloud WITH PASSWORD 'CHANGE_ME_1';
CREATE DATABASE nextcloud OWNER nextcloud;

CREATE USER joplin WITH PASSWORD 'CHANGE_ME_1';
CREATE DATABASE joplin OWNER joplin;
EOF

Add the init script volume to the postgres service:

    volumes:
      - ./data/postgres:/var/lib/postgresql/data
      - ./config/postgres:/docker-entrypoint-initdb.d

6. Deploy

docker compose up -d

7. Set Up Ollama

# Pull a model (choose based on your hardware)
# 8GB RAM: small model
docker exec ollama ollama pull phi3:mini

# 16GB RAM: medium model
docker exec ollama ollama pull llama3.2

# 32GB+ RAM or GPU: large model
docker exec ollama ollama pull llama3.1:70b

# Test
docker exec ollama ollama run llama3.2 "Summarize the key points of project management"

In your browser: Settings → Search Engine → Add custom → https://search.office.local/search?q=%s

9. Connect Clients

Nextcloud: Install apps on phone and desktop. Sync calendar and contacts in phone settings (CalDAV/CardDAV).

Joplin: Install Joplin desktop and mobile apps. Settings → Sync → Joplin Server → enter your server URL.

Vaultwarden: Install Bitwarden apps. Settings → Self-hosted → enter vault URL.

FreshRSS: Add your favorite news sources, blogs, and YouTube channels as RSS feeds.

Wallabag: Install browser extension. One click saves any article.


Failure Modes

Component Failure scenario Impact Recovery
PostgreSQL Database corruption Nextcloud and Joplin lose metadata Restore from backup. Files on disk are intact.
Nextcloud PHP error Files inaccessible via web Files still on disk. Fix via occ maintenance:repair
Ollama OOM (out of memory) AI assistant crashes Use a smaller model. Restart: docker compose restart ollama
SearXNG Upstream engines blocked Search returns no results Switch engines in SearXNG settings. Self-hosted search still works.
FreshRSS Feed fetch failure New articles not appearing Check feed URLs. Some sites block RSS scraping.
Wallabag Disk full Can’t save new articles Clean old articles or expand storage.
Vaultwarden Database corruption Passwords inaccessible online Bitwarden apps have offline cache. Restore from backup.

Blast radius: PostgreSQL is the highest-impact component — takes down Nextcloud and Joplin if it fails. Ollama, SearXNG, FreshRSS, and Wallabag are independent — each failure is isolated.


What Replaces What

Need You used to use Now you use Autonomy gain
Files + calendar Google Workspace Nextcloud A0 → A3
Notes Notion / Google Keep Joplin A0 → A3
AI assistant ChatGPT / Claude Ollama A0 → A3
Web search Google Search SearXNG A0 → A3
News Google News / Twitter FreshRSS A0 → A3
Read later Pocket / Instapaper Wallabag A0 → A3
Passwords LastPass / 1Password Vaultwarden A0 → A3

Cost

Item One-time Monthly
Mini PC (16 GB RAM recommended) $200–400
1 TB SSD $60–100
Domain (optional) $1/month
Electricity ~$5/month
Total $260–500 ~$6/month

Compare: Google Workspace ($7/mo) + ChatGPT Plus ($20/mo) + Pocket Premium ($5/mo) + 1Password ($3/mo) = $35/month = $420/year. Hardware pays for itself in under a year.


This site uses Just the Docs, a documentation theme for Jekyll.