Skip to content

Instantly share code, notes, and snippets.

@cybik
Last active June 8, 2020 16:44
Show Gist options
  • Save cybik/84379480aeaec2a30b11b2c33c5921b4 to your computer and use it in GitHub Desktop.
Save cybik/84379480aeaec2a30b11b2c33c5921b4 to your computer and use it in GitHub Desktop.
Ramblings on how I deployed my Mastodon stack.

My own Mastodon Baseline

QA before anything

What is this?

Just a quick way to throw Mastodon at a docker stack. I've not used podman and buildah because I'm f///ing lazy.

Why?

The current mastodon readmes don't even cover docker-compose. Also they're a bit crap. Also also I had to sleuth the internet for a bit just to get everything set up right.

Okay but still why? You're on Twitter!

I'm allowed to want to try to wean off the blasted Social Portal x SMS relay service, aren't I?

Technologies used

Here's what I'm using with/for this

  • Docker
  • Docker-compose
  • Apache2
  • ACME/letsencrypt

Bonus points

  • OpenWRT with the Apache2 and ACME as your reverse proxy heh

WARNING

Mastodon is enforcing https now. You'll basically HAVE to run it behind a reverse proxy. See 04 for my own thing.

How do I use this?

  • First, clone git@github.com:tootsuite/mastodon.git locally
    • Put it in /srv, that sounds like a good idea
  • Then, copy the contents of the docker-compose.yml file in this gist, so that docker-compose pulls the needful appropriately
  • Run docker-compose run --rm web bundle exec rake mastodon:setup to get a properly-constructed prodfile
    • Save it as .env.production in your mastodon clone
    • Notice that .env.production is used in the docker-compose file in such a way that you need to stop-start containers if you change it.
  • docker-compose up -d should start the containers and get everything up and running.
    • It'll be listening on localhost only with the upstream docker-compose.yml
    • If you copypastaed my compose file, it listens on all IPs. DO NOT USE THIS ON A DMZ'D HOST for the love of sanity.

Issues you may face

  • At one point, my streaming container was being refused any and all connections to redis. It might have had something to do with the fact that I was trying to deploy a new service within that stack, and that service somehow told redis "no password? that's a bad"
    • Solution:
      • docker exec -it <CONTAINER> sh and throw a bunch of commands at redis to:
        • set a password on the admin account
        • create an account called redis
        • set a password on the redis account
      • update your .env.production file with the new redis auth info

Reverse Proxy

Now that Mastodon is demanding https or else go fish (good sh!t), you'll also need a reverse proxy. To get EVERYTHING running swell, I had to scour the search engines and finally fell on this cool page

As-is, it didn't work since my own setup is different and my router does the whole redirection-to-multiple-different-hosts-and-or-ports thing, so here's how I did the part of the reverse proxy config itself. Note that I may have also used other pages as cross-inspiration; this is merely the end result that worked for me. YMMV etc.

<VirtualHost _default_:443>
        DocumentRoot "/some/stuff"
        ServerName your.own.mastodon.target.dns.name
        ServerAdmin your@own.email
        ErrorLog "/path/to/your/target_error_log"
        TransferLog "/path/to/your/target_access_log"

        SSLEngine on
        Protocols h2 http/1.1

        SSLCertificateFile "/path/to/your.acmed.domain.cer"
        SSLCertificateKeyFile "/path/to/your.acmed.domain.key"
        SSLCertificateChainFile "/path/to/fullchain.cer"
        SSLCACertificateFile "/path/to/ca.cer"

        Header always set Referrer-Policy "strict-origin-when-cross-origin"
        Header always set Strict-Transport-Security "max-age=31536000"

        <LocationMatch "^/(assets|avatars|emoji|headers|packs|sounds|system)">
                Header always set Cache-Control "public, max-age=31536000, immutable"
                Require all granted
        </LocationMatch>

        <Location "/">
                Require all granted
        </Location>

        ProxyRequests Off
        ProxyPreserveHost On
        SSLProxyEngine On
        AllowEncodedSlashes NoDecode
        RequestHeader set X-Forwarded-Proto "https"
        RequestHeader set X-Forwarded-Port "443"
        ProxyAddHeaders On

        ProxyPass /api/v1/streaming ws://<TARGET>:4000/
        ProxyPassReverse /api/v1/streaming ws://<TARGET>:4000/
        ProxyPass / http://<TARGET>:3000/
        ProxyPassReverse / http://<TARGET>:3000/


        <FilesMatch "\.(cgi|shtml|phtml|php)$">
                SSLOptions +StdEnvVars
        </FilesMatch>
        <Directory "/usr/share/apache2/cgi-bin">
                SSLOptions +StdEnvVars
        </Directory>

        BrowserMatch "MSIE [2-5]" \
                 nokeepalive ssl-unclean-shutdown \
                 downgrade-1.0 force-response-1.0

        CustomLog "LOGPATH" \
                 "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"

        ErrorDocument 500 /500.html
        ErrorDocument 501 /500.html
        ErrorDocument 502 /500.html
        ErrorDocument 503 /500.html
        ErrorDocument 504 /500.html
</VirtualHost>

version: '3'
services:
db:
restart: always
image: postgres:9.6-alpine
shm_size: 256mb
networks:
- internal_network
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
environment:
POSTGRES_HOST_AUTH_METHOD: "trust"
volumes:
- ./postgres:/var/lib/postgresql/data
redis:
restart: always
image: redis:6.0-alpine
networks:
- internal_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
volumes:
- ./redis:/data
# es:
# restart: always
# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.1.3
# environment:
# - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
# networks:
# - internal_network
# healthcheck:
# test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
# volumes:
# - ./elasticsearch:/usr/share/elasticsearch/data
web:
#build: .
image: tootsuite/mastodon:v3.1.4
restart: always
env_file: .env.production
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
networks:
- external_network
- internal_network
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:3000/health || exit 1"]
ports:
- "3000:3000"
depends_on:
- db
- redis
# - es
volumes:
- ./public/system:/mastodon/public/system
streaming:
#build: .
image: tootsuite/mastodon:v3.1.4
restart: always
env_file: .env.production
command: node ./streaming
networks:
- external_network
- internal_network
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
ports:
- "4000:4000"
depends_on:
- db
- redis
sidekiq:
#build: .
image: tootsuite/mastodon:v3.1.4
restart: always
env_file: .env.production
command: bundle exec sidekiq
depends_on:
- db
- redis
networks:
- external_network
- internal_network
volumes:
- ./public/system:/mastodon/public/system
## Uncomment to enable federation with tor instances along with adding the following ENV variables
## http_proxy=http://privoxy:8118
## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
# tor:
# image: sirboops/tor
# networks:
# - external_network
# - internal_network
#
# privoxy:
# image: sirboops/privoxy
# volumes:
# - ./priv-config:/opt/config
# networks:
# - external_network
# - internal_network
networks:
external_network:
internal_network:
internal: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment