Introduction

This guide is meant to be a top level overview on how to get a Hugo based static site set up on both the clear-web, and the dark-web. Both sites will host the exact same content, have custom URLs, and will be publically accessible. The intent is to string together some prior posts into a cohesive walkthrough, with less detail than the individual guides linked below.

Install Docker -> Hugo Setup -> Tor & Cloudflare Tunnel

flowchart

Requirements

Suggestions

Folder Structure

├──websitestack
│  ├──compose.yaml
│  ├──hugo-generator
│  │  ├──hugo.yaml
│  │  └──[THE REST OF THE HUGO FOLDERS]
│  ├──torrc
│  ├──keys
└──└──sites
│  │  ├──tun
│  │  └──tor

Folder Setup

mkdir -p websitestack/{hugo-generator,torrc,keys,tor-site,cloudflare-site}

File Setup

touch websitestack/compose.yaml
touch websitestack/torrc

Place Key Files (Optional)

  1. Generate custom keys with mkp224o (more in this post).
  2. Place the keys into the keys directory.

Permissions

chmod 700 ./keys 
sudo chown -R root:root ./keys

1. Build Static Site

Refer to the prior post and to the Hugo documentation for the site setup instructions.

There is a one line configuration difference between the two sites: baseURL:.

Cloudflare Tunnel Version

  1. Update the baseURL
hugo.yaml
baseURL: "https://[YOUR DOMAIN].SOMETHING"
languageCode: en-us
title: [TITLE]
theme: ["THEME_NAME"]...
  1. Generate the static site in the hugo-generator directory
hugo build --minify --cleanDestinationDir -d ../cloudflare-site

Tor Hidden Service Version

A note from this blog post, by sethforprivacy, was essential to getting the correct configuration for hugo.yaml. The baseURL needs to be your .onion hostname.

If you generated a vanity url, cat the hostname file for your url. Otherwise follow the steps in #Troubleshooting to get an automatically generated url.

  1. Update the baseURL
hugo.yaml
baseURL: "http://[ONION ADDRESS].onion"
languageCode: en-us
title: [TITLE]
theme: ["THEME_NAME"]...
  1. Generate the static site in the hugo-generator directory
hugo build --minify --cleanDestinationDir -d ../tor-site

2. Setup Docker Compose

compose.yaml
#NGINX
services:
    tor-site:
        container_name: tor-site
        image: nginx:alpine
        cap_drop:
            - ALL
        cap_add:
            - CHOWN
            - SETGID
            - SETUID
        volumes:
            - ./sites/tor:/usr/share/nginx/html:ro
        networks:
        - tor_network

    cloudflare-site:
        container_name: cloudflare-site
        image: nginx:alpine
        cap_drop:
            - ALL
        cap_add:
            - CHOWN
            - SETGID
            - SETUID
        volumes:
            - ./sites/tun:/usr/share/nginx/html:ro
        networks:
            - cloudflared
#NETWORKING
    tor:
        container_name: webstack-tor
        volumes:
            - ./torrc:/etc/tor/torrc:ro
            - ./keys:/var/lib/tor/hidden_service/
        image: alpine:latest
        entrypoint: sh -c "apk add --no-cache tor && tor -f /etc/tor/torrc"
        security_opt:
          - no-new-privileges:true
        cap_drop:
           - ALL
        cap_add:
           - NET_BIND_SERVICE
        networks:
            - tor_network
        depends_on:
           - tor-site
    cloudflared:
        image: cloudflare/cloudflared:latest
        container_name: webstack-cloudflare
        restart: unless-stopped
        command: tunnel run
        networks:
            - cloudflared
        environment:
            - TUNNEL_TOKEN=${CLOUDFLARE_KEY}
        cap_drop:
            - ALL

networks:
  tor_network:
  cloudflared:

3. Configure Container Networking

Cloudflare Version

Get your TOKEN by clicking Add a Connector from the Cloudflare panel (networks>connectors). Ensure you add this to compose.yaml in place of [TUNNEL KEY]. Best practice is to use a .env file to store the variable.

Set up a new Tunnel following the documentation. In place of the Service Url, use the http type setting, and the url cloudflare-site:80.

Tor Version

Set up the torrc file:

# Standard Tor config
DataDirectory /var/lib/tor
# Define the Hidden Service
HiddenServiceDir /var/lib/tor/hidden_service/
HiddenServicePort 80 tor-site:80

Conclusion

Run:

docker compose up -d

You will likely encounter errors. Check the troubleshooting guide below for updates. These setups could just as easily be two separate stacks (one for cloudflare, one for tor). This guide is likely over complicated.

If you want even more overcomplication, think about how we could build a docker container for each of these sites, to decrease the number of files we pass through from the docker host.

Troubleshooting

  • You don’t have Tor keys
    1. Finish the guide, the site will not work initially
    2. Once the Tor container runs successfully, it will generate keys for you
    3. In the project folder run sudo cat keys/hostname to see your .onion address
    4. Set baseURL to the .onion address
    5. Rebuild the Hugo site
  • Use docker logs [CONTAINER NAME] to see what’s going on, start with the egress and work backwards

References

Hugo

Tor Hidden Service

Cloudflare Tunnels