The Docker craze is in full force and shows no signs of slowing down. Software containers seem to be one of the most significant game changers for DevOps since virtual machines. Docker’s development is active and its supporting projects (Compose, Machine, Swarm) are maturing quickly. Though the complete Docker ecosystem is interesting enough, Docker Compose has caught my attention as the most exciting, mainly due to its ability to spin up an entire infrastructure (with multiple server components) using a single command.
There are many benefits to software containerization of this kind — simplicity is one of them (developers typically prefer to concentrate on building software rather than infrastructure). Therefore, I thought it would be interesting to demonstrate its power by using Docker Compose to publish a hidden Tor service.
I started by searching for an interesting demo web application that was already setup for Compose. The YouTube Audio Downloader project was a great choice since it’s comprised of multiple server components. Next, I downloaded the source files and started customizing the docker-compose.yml recipe. Feel free to follow along with my source code here.
postgresql: image: sameersbn/postgresql:9.4-11 environment: - DB_NAME=youtubeadl - DB_USER=youtubeadl - DB_PASS=password volumes: - ~/dockerfiles/youtube-audio-dl/postgresql:/var/lib/postgresql expose: - "5432" rabbitmq: image: jcalazan/rabbitmq expose: - "15672" # NOTES: # - The C_FORCE_ROOT variable allows celery to run as the root user. celery: build: django/ environment: - C_FORCE_ROOT=true - DATABASE_HOST=postgresql - BROKER_URL=amqp://guest:guest@rabbitmq// working_dir: /youtube-audio-dl command: bash -c "sleep 3 && celery -A youtubeadl worker -E -l info --concurrency=3" volumes: - .:/youtube-audio-dl links: - postgresql - rabbitmq # NOTES: # - The C_FORCE_ROOT variable allows celery to run as the root user. flower: build: django/ environment: - C_FORCE_ROOT=true - DATABASE_HOST=postgresql - BROKER_URL=amqp://guest:guest@rabbitmq// working_dir: /youtube-audio-dl command: bash -c "sleep 3 && celery -A youtubeadl flower --port=5555" volumes: - .:/youtube-audio-dl expose: - "5555" links: - postgresql - rabbitmq django: build: django/ environment: - DATABASE_HOST=postgresql - BROKER_URL=amqp://guest:guest@rabbitmq// working_dir: /youtube-audio-dl command: bash -c "sleep 3 && python manage.py migrate --noinput && python manage.py runserver_plus 0.0.0.0:80" volumes: - .:/youtube-audio-dl expose: - "80" links: - postgresql - rabbitmq tor: build: tor-hidden-service/ links: - django:web
As you can see, I’ve cleaned up and simplified the compose file (compared to the original). Note the swapping of the ports
declarations in favor of expose
. This was not absolutely required but since I’m publishing a Tor service, I’m not interested in mapping any of the ports to the host machine. This also means that the web application will be inaccessible outside of the Tor network.
Next, note the addition of the tor
service. This service can be used with any docker-compose recipe to publish any web application as a hidden Tor service — simply link the container with the exposed web application and it handles the rest. Following the source code found in the build directory tor-hidden-service/
, you’ll notice that I’m extending the patrickod/tor-hidden-service image while adding a few customizations:
FROM patrickod/tor-hidden-service ADD ./start-tor /bin/start-tor RUN chmod +x /bin/start-tor ADD ./get-tor-hostname /bin/get-tor-hostname RUN chmod +x /bin/get-tor-hostname
I added the get-tor-hostname
script as a convenient way to access the service’s Tor onion address:
#!/bin/bash HOSTNAME='/var/lib/tor/hidden-service/hostname' while [ ! -f $HOSTNAME ]; do sleep 1; done echo 'Your onion address is' $(cat $HOSTNAME)
Putting it all together, we can run the demo by first cloning the repo.
git clone https://github.com/rwestergren/docker-compose-tor-demo.git
Tor depends on an accurate system clock, so make sure your Docker host’s clock is up-to-date. Change into the demo directory and run the application: docker-compose up -d
After the images are pulled and everything is up and running, you can see the status of the containers by running docker-compose ps
. Note that the ports are exposed to other containers, but not published to the host machine:
Lastly, get the server’s onion address by checking the tor logs: docker-compose logs tor
. Make sure to test the onion address using the Tor browser (it should work behind NAT/firewall).
Hopefully, this helps demonstrate the power of Docker Compose (and Docker in general) while making it easier to host web applications on the Tor network.
Disclaimer
The source code and included Docker images are for demo purposes only — they are not meant to be a production-ready solution for hosting hidden Tor services. Find more information on securely hosting Tor hidden services here.
Share this:

