diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index eb9527756..9a9cead62 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -1,23 +1,39 @@ -version: '2.2' +version: "3.3" + +networks: + # communication to web and clients + lemmyexternalproxy: + # communication between lemmy services + lemmyinternal: + driver: bridge + internal: true services: - postgres: - image: postgres:14-alpine - environment: - - POSTGRES_USER=lemmy - - POSTGRES_PASSWORD=password - - POSTGRES_DB=lemmy + proxy: + image: nginx:1-alpine + networks: + - lemmyinternal + - lemmyexternalproxy + ports: + # only ports facing any connection from outside + - 80:80 + - 443:443 volumes: - - ./volumes/postgres:/var/lib/postgresql/data + - ./nginx.conf:/etc/nginx/nginx.conf:ro + # setup your certbot and letsencrypt config + - ./certbot:/var/www/certbot + - ./letsencrypt:/etc/letsencrypt/live restart: always + depends_on: + - pictrs + - lemmy-ui lemmy: - image: dessalines/lemmy:0.16.6 - ports: - - "127.0.0.1:8536:8536" - - "127.0.0.1:6669:6669" + image: dessalines/lemmy:0.16.7 + hostname: lemmy + networks: + - lemmyinternal restart: always - environment: - RUST_LOG="warn,lemmy_server=info,lemmy_api=info,lemmy_api_common=info,lemmy_api_crud=info,lemmy_apub=info,lemmy_db_schema=info,lemmy_db_views=info,lemmy_db_views_actor=info,lemmy_db_views_moderator=info,lemmy_routes=info,lemmy_utils=info,lemmy_websocket=info" volumes: - ./lemmy.hjson:/config/config.hjson @@ -26,24 +42,44 @@ services: - pictrs lemmy-ui: - image: dessalines/lemmy-ui:0.16.6 - ports: - - "127.0.0.1:1235:1234" - restart: always + image: dessalines/lemmy-ui:0.16.7 + networks: + - lemmyinternal environment: + # this needs to match the hostname defined in the lemmy service - LEMMY_INTERNAL_HOST=lemmy:8536 - - LEMMY_EXTERNAL_HOST=localhost:8536 + # set the outside hostname here + - LEMMY_EXTERNAL_HOST=localhost:1236 - LEMMY_HTTPS=true - depends_on: + depends_on: - lemmy + restart: always pictrs: image: asonix/pictrs:0.3.1 - ports: - - "127.0.0.1:8537:8080" - - "127.0.0.1:6670:6669" + # this needs to match the pictrs url in lemmy.hjson + hostname: pictrs + # we can set options to pictrs like this, here we set max. image size and forced format for conversion + # entrypoint: /sbin/tini -- /usr/local/bin/pict-rs -p /mnt -m 4 --image-format webp + networks: + - lemmyinternal + environment: + - PICTRS__API_KEY=API_KEY user: 991:991 volumes: - ./volumes/pictrs:/mnt restart: always + postgres: + image: postgres:14-alpine + # this needs to match the database host in lemmy.hson + hostname: postgres + networks: + - lemmyinternal + environment: + - POSTGRES_USER=lemmy + - POSTGRES_PASSWORD=password + - POSTGRES_DB=lemmy + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + restart: always diff --git a/docker/lemmy.hjson b/docker/prod/lemmy.hjson similarity index 55% rename from docker/lemmy.hjson rename to docker/prod/lemmy.hjson index 585f14468..edbb25356 100644 --- a/docker/lemmy.hjson +++ b/docker/prod/lemmy.hjson @@ -1,6 +1,7 @@ { # for more info about the config, check out the documentation # https://join-lemmy.org/docs/en/administration/configuration.html + # only few config options are covered in this example config setup: { # username for the admin user @@ -8,22 +9,22 @@ # password for the admin user admin_password: "lemmylemmy" # name of the site (can be changed later) - site_name: "lemmy-test" + site_name: "mylemmyinstance" } - opentelemetry_url: "http://otel:4137" - # the domain name of your instance (eg "lemmy.ml") hostname: "mydomain.ml" # address where lemmy should listen for incoming requests bind: "0.0.0.0" # port where lemmy should listen for incoming requests port: 8536 + # Whether the site is available over TLS. Needs to be true for federation to work. + tls_enabled: true + + # pictrs host + pictrs_url: "http://pictrs:8080" + # settings related to the postgresql database - pictrs_config: { - url: "http://pictrs:8080" - api_key: "API_KEY" - } database: { # name of the postgres database for lemmy database: "lemmy" @@ -38,22 +39,5 @@ # maximum number of active sql connections pool_size: 5 } - slur_filter: - ''' - (fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?) - ''' -# # optional: email sending configuration -# email: { -# # hostname and port of the smtp server -# smtp_server: "" -# # login name for smtp server -# smtp_login: "" -# # password to login to the smtp server -# smtp_password: "" -# # address to send emails from, eg "noreply@your-instance.com" -# smtp_from_address: "" -# # whether or not smtp connections should use tls -# use_tls: true -# } } diff --git a/docker/prod/nginx.conf b/docker/prod/nginx.conf new file mode 100644 index 000000000..3e0bc3491 --- /dev/null +++ b/docker/prod/nginx.conf @@ -0,0 +1,152 @@ +# nginx example config +# replace {{yourdomain}} and review the certbot/letsencrypt config +worker_processes 1; +events { + worker_connections 1024; +} +http { + limit_req_zone $binary_remote_addr zone={{yourdomain}}_ratelimit:10m rate=1r/s; + + upstream lemmy { + # this needs to map to the lemmy (server) docker service hostname + server "lemmy:8536"; + } + upstream lemmy-ui { + # this needs to map to the lemmy-ui docker service hostname + server "lemmy-ui:1234"; + } + + server { + # allow letsencrypt challenge + # redirect everything else to 443 + listen 80; + listen [::]:80; + server_name {{yourdomain}}; + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } + } + + server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{yourdomain}}; + + ssl_certificate /etc/letsencrypt/live/{{yourdomain}}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{yourdomain}}/privkey.pem; + + # Various TLS hardening settings + # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets on; + ssl_stapling on; + ssl_stapling_verify on; + + # Hide nginx version + server_tokens off; + + # Enable compression for JS/CSS/HTML bundle, for improved client load times. + # It might be nice to compress JSON, but leaving that out to protect against potential + # compression+encryption information leak attacks like BREACH. + gzip on; + gzip_types text/css application/javascript image/svg+xml; + gzip_vary on; + + # Only connect to this site via HTTPS for the two years + add_header Strict-Transport-Security "max-age=63072000"; + + # Various content security headers + add_header Referrer-Policy "same-origin"; + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "DENY"; + add_header X-XSS-Protection "1; mode=block"; + + # Upload limit for pictrs + client_max_body_size 20M; + + # frontend + location / { + # distinguish between ui requests and backend + # don't change lemmy-ui or lemmy here, they refer to the upstream definitions on top + set $proxpass "http://lemmy-ui"; + + if ($http_accept = "application/activity+json") { + set $proxpass "http://lemmy"; + } + if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") { + set $proxpass "http://lemmy"; + } + if ($request_method = POST) { + set $proxpass "http://lemmy"; + } + proxy_pass $proxpass; + + rewrite ^(.+)/+$ $1 permanent; + + # Send actual client IP upstream + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # backend + location ~ ^/(api|feeds|nodeinfo|.well-known) { + proxy_pass "http://lemmy"; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Rate limit + limit_req zone={{yourdomain}}_ratelimit burst=30 nodelay; + + # Add IP forwarding headers + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # pictrs only - for adding browser cache control. + location ~ ^/(pictrs) { + # allow browser cache, images never update, we can apply long term cache + expires 120d; + add_header Pragma "public"; + add_header Cache-Control "public"; + + proxy_pass "http://lemmy"; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Rate limit + limit_req zone={{yourdomain}}_ratelimit burst=30 nodelay; + + # Add IP forwarding headers + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # Redirect pictshare images to pictrs + location ~ /pictshare/(.*)$ { + return 301 /pictrs/image/$1; + } + } + + # Anonymize IP addresses + # https://www.supertechcrew.com/anonymizing-logs-nginx-apache/ + map $remote_addr $remote_addr_anon { + ~(?P\d+\.\d+\.\d+)\. $ip.0; + ~(?P[^:]+:[^:]+): $ip::; + 127.0.0.1 $remote_addr; + ::1 $remote_addr; + default 0.0.0.0; + } + access_log /var/log/nginx/access.log combined; +} \ No newline at end of file