diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0a8300268353..64ec35f292e5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,15 +1,58 @@ -version: "3.8" - services: - monkeytype-frontend: - container_name: monkeytype-frontend - image: monkeytype/monkeytype-frontend:latest - ports: - - "${HTTP_PORT:-8080}:80" + monkeytype-proxy: + container_name: monkeytype-proxy + image: traefik:v3.7 restart: on-failure depends_on: monkeytype-backend: condition: service_healthy + monkeytype-frontend: + condition: service_healthy + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + #Dashboard disabled for security (uncomment only for debugging behind VPN) + #- "8080:8080" + networks: + - public + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./letsencrypt:/letsencrypt + command: + - "--providers.docker=true" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + + # enable for HTTPS + #- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}" + #- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + #- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" + #- "--entrypoints.web.http.redirections.entrypoint.to=websecure" + #- "--entrypoints.web.http.redirections.entrypoint.scheme=https" + + # for debugging only + #- "--api.insecure=true" + #- "--log.level=DEBUG" + + monkeytype-frontend: + container_name: monkeytype-frontend + image: monkeytype/monkeytype-frontend:latest + #uncomment if you want to expose the frontend server directly (not recommended) + #ports: + # - "${FRONTEND_PORT:-8080}:80" + restart: on-failure + networks: + - public + labels: + - "traefik.http.routers.frontend.rule=PathPrefix(`/`)" + - "traefik.http.routers.frontend.entrypoints=web,websecure" + - "traefik.http.routers.frontend.priority=25" + - "traefik.http.services.frontend.loadbalancer.server.port=80" + # enable for HTTPS + #- "traefik.http.routers.frontend.rule=Host(`${DOMAIN}`)" + #- "traefik.http.routers.frontend.tls=true" + #- "traefik.http.routers.frontend.tls.certresolver=letsencrypt" + #- "traefik.http.routers.frontend.tls.domains[0].main=${DOMAIN}" env_file: - path: ./.env required: true @@ -20,20 +63,30 @@ services: - FIREBASE_STORAGEBUCKET=${FIREBASE_STORAGEBUCKET:-} - FIREBASE_MESSAGINGSENDERID=${FIREBASE_MESSAGINGSENDERID:-} - FIREBASE_APPID=${FIREBASE_APPID:-} - - MONKEYTYPE_BACKENDURL=${MONKEYTYPE_BACKENDURL} + - MONKEYTYPE_BACKENDURL=${BASE_URL:-http://localhost}/api - RECAPTCHA_SITE_KEY=${RECAPTCHA_SITE_KEY:-} + healthcheck: + test: ["CMD", "nc", "-z", "localhost", "80"] + interval: 5s + timeout: 10s + retries: 15 + start_period: 5s monkeytype-backend: container_name: monkeytype-backend image: monkeytype/monkeytype-backend:latest - ports: - - "${BACKEND_PORT:-5005}:5005" + #uncomment if you want to expose the backend server (not recommended) + #ports: + # - "${BACKEND_PORT:-5005}:5005" + networks: + - public # access to the internet, e.g. google firebase + - isolated # access to the databases restart: on-failure environment: - DB_NAME=monkeytype - DB_URI=mongodb://monkeytype-mongodb:27017 - REDIS_URI=redis://monkeytype-redis:6379 - - FRONTEND_URL=${MONKEYTYPE_FRONTENDURL} + - FRONTEND_URL=${BASE_URL:-http://localhost} - RECAPTCHA_SECRET=${RECAPTCHA_SECRET:-} - EMAIL_HOST=${EMAIL_HOST:-} - EMAIL_PORT=${EMAIL_PORT:-} @@ -56,18 +109,32 @@ services: monkeytype-mongodb: condition: service_healthy healthcheck: - test: ["CMD", "nc", "-z", "-v", "localhost", "5005"] + test: ["CMD", "nc", "-z", "localhost", "5005"] interval: 5s timeout: 10s retries: 15 start_period: 5s + labels: + - "traefik.http.middlewares.backend-prefix.stripprefix.prefixes=/api" + - "traefik.http.middlewares.backend-headers.headers.customrequestheaders.CF-Connecting-IP=" + - "traefik.http.routers.backend.middlewares=backend-prefix,backend-headers" + - "traefik.http.routers.backend.rule=PathPrefix(`/api`)" + - "traefik.http.routers.backend.priority=100" + - "traefik.http.routers.backend.entrypoints=web" + - "traefik.http.services.backend.loadbalancer.server.port=5005" + - "traefik.enable=true" + - "traefik.docker.network=monkeytype-public" monkeytype-redis: container_name: monkeytype-redis image: redis:6.2.6 - #uncomment if you want to expose the redis server + #uncomment if you want to expose the redis server (not recommended) #ports: # - "${REDIS_PORT:-6379}:6379" + networks: + - isolated + #uncomment if you want to expose the redis server (not recommended) + #- public restart: on-failure volumes: - redis-data:/data @@ -84,9 +151,13 @@ services: restart: on-failure volumes: - mongo-data:/data/db - #uncomment if you want to expose the mongodb server + #uncomment if you want to expose the mongodb server (not recommended) #ports: # - "${MONGO_PORT:-27017}:27017" + networks: + - isolated + #uncomment if you want to expose the mongodb server (not recommended) + #- public healthcheck: test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet interval: 5s @@ -99,3 +170,10 @@ volumes: name: monkeytype_mongo_data redis-data: name: monkeytype_redis_data + +networks: + isolated: + name: monkeytype-isolated + internal: true + public: + name: monkeytype-public diff --git a/docker/example.env b/docker/example.env index b0e3f94172a2..907245753148 100644 --- a/docker/example.env +++ b/docker/example.env @@ -2,11 +2,13 @@ ### === Required Config === -# URL of the frontend (accessible by browser) -MONKEYTYPE_FRONTENDURL=http://myserver:8080 - -# URL of the backend (accessible by browser) -MONKEYTYPE_BACKENDURL=http://myserver:5005 +# By default your installation is available on http://localhost +# If you want to expose the installation on a network (private or public) +# you need to have a domain and enable the HTTPS marked entries in the docker-compose.yml +# and uncomment and set the following +#DOMAIN= +#BASE_URL= +#ACME_EMAIL= ### === Optional: Google reCAPTCHA === @@ -38,7 +40,9 @@ RECAPTCHA_SECRET= ### === Optional: Custom Ports === -# HTTP_PORT=8080 +# HTTP_PORT=80 +# HTTPS_PORT=443 +# FRONTEND_PORT=8080 # BACKEND_PORT=5005 # REDIS_PORT=6379 # MONGO_PORT=27017 diff --git a/docs/SELF_HOSTING.md b/docs/SELF_HOSTING.md index dea22c83821e..fb6739423135 100644 --- a/docs/SELF_HOSTING.md +++ b/docs/SELF_HOSTING.md @@ -35,13 +35,25 @@ - create an `.env` file, you can copy the content from the [example.env](https://github.com/monkeytypegame/monkeytype/tree/master/docker/example.env). - download the [backend-configuration.json](https://github.com/monkeytypegame/monkeytype/tree/master/docker/backend-configuration.json) - run `docker compose up -d` -- after the command exits successfully you can access [http://localhost:8080](http://localhost:8080) +- after the command exits successfully you can access [http://localhost](http://localhost) ### Hosting over the network (HTTPS) If you plan to access your self-hosted Monkeytype instance over a local network or the internet (not using `localhost`), **you must serve it over HTTPS**. Modern browsers restrict key web features, such as `crypto.randomUUID`, to secure contexts. Accessing the site via HTTP over a network will cause the frontend to crash with errors like `Uncaught TypeError: crypto.randomUUID is not a function`. -To solve this, you need to place a reverse proxy (like Nginx, Caddy, or Traefik) in front of your containers to handle HTTPS/TLS termination. +#### Enable HTTPS + +Update the `.env` file and uncomment these lines and set the values based on your domain. + +``` +DOMAIN=mydomain.com +BASE_URL=https://mydomain.com +ACME_EMAIL=certmanager@mydomain.com +``` + +Update the `docker-compose.yml` and uncomment all lines marked with `# enable for HTTPS`. + + #### Troubleshooting Frontend Connection Issues @@ -49,8 +61,8 @@ If your reverse proxy is up but you see errors like `Looks like the server is ex Ensure you configure the frontend to talk to your secure backend URL by following these rules in your `.env` file: -1. **Update the frontend and backend URL:** Set `MONKEYTYPE_FRONTENDURL` and `MONKEYTYPE_BACKENDURL` to your full HTTPS backend domain. -2. **Do not include a trailing slash:** Ensure the URL does not end with a `/` (e.g., use `https://api.yourdomain.com`, **not** `https://api.yourdomain.com/`). A trailing slash will cause `404 Not Found` errors due to double slashes in the API calls (like `//configuration`). +1. **Update the frontend and backend URL:** Set `DOMAIN` and `BASE_URL` correctly, usually `BASE_URL` is `https://DOMAIN`. +2. **Do not include a trailing slash:** Ensure the URL does not end with a `/` (e.g., use `https://yourdomain.com`, **not** `https://yourdomain.com/`). A trailing slash will cause `404 Not Found` errors due to double slashes in the API calls (like `//configuration`). 3. **Force container recreation:** Monkeytype is a Single Page Application (SPA), meaning environment variables are baked into the static JavaScript files during startup. If you change your `.env`, you must completely recreate the container for the changes to apply: ```bash