diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..f579acb186ff94922a3d7ab672e304915758e46d
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+**/node_modules/**
+**/Dockerfile
diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index baa3a76476dfead9b6abb9d44be7fcc4ae2306b8..218d9ad1bfe58411eb2fd5fa8b585724890e258b 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -159,5 +159,5 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
-          msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
+          msg: Environment deployed at https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
           check_for_duplicate_msg: true
diff --git a/README.md b/README.md
index faafed98ae262aa4e257c18dc80a5a575e2149ff..5945ac489a9a446f0912e74ced13261fa67c6dd2 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ In Work Adventure, you can move around your office and talk to your colleagues (
 triggered when you move next to a colleague).
 
 
-## Getting started
+## Setting up a development environment
 
 Install Docker.
 
@@ -101,5 +101,7 @@ Vagrant destroy
 * `Vagrant halt`: stop your VM Vagrant.
 * `Vagrant destroy`: delete your VM Vagrant.
 
-## Features developed
-You have more details of features developed in back [README.md](./back/README.md).
+## Setting up a production environment
+
+The way you set up your production environment will highly depend on your servers.
+We provide a production ready `docker-compose` file that you can use as a good starting point in the [contrib/docker](https://github.com/thecodingmachine/workadventure/tree/master/contrib/docker) directory.
diff --git a/back/Dockerfile b/back/Dockerfile
index 5ec83a8f65a4e2aeb4da2ee091b487e90590e5da..e95145cdaab380b0d17c431fda9dc21530b10b14 100644
--- a/back/Dockerfile
+++ b/back/Dockerfile
@@ -1,16 +1,26 @@
-FROM thecodingmachine/workadventure-back-base:latest as builder
-WORKDIR /var/www/messages
-COPY --chown=docker:docker messages .
+# protobuf build
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
+WORKDIR /usr/src
+COPY messages .
 RUN yarn install && yarn proto
 
-FROM thecodingmachine/nodejs:12
-
-COPY --chown=docker:docker back .
-COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
+# typescript build
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
+WORKDIR /usr/src
+COPY back/yarn.lock back/package.json ./
 RUN yarn install
-
+COPY back .
+COPY --from=builder /usr/src/generated src/Messages/generated
 ENV NODE_ENV=production
 RUN yarn run tsc
 
-CMD ["yarn", "run", "runprod"]
+# final production image
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
+WORKDIR /usr/src
+COPY back/yarn.lock back/package.json ./
+COPY --from=builder2 /usr/src/dist /usr/src/dist
+ENV NODE_ENV=production
+RUN yarn install --production
 
+USER node
+CMD ["yarn", "run", "runprod"]
diff --git a/back/README.md b/back/README.md
deleted file mode 100644
index 8a78f403e0e280de6ca41d7050dd906d06bbb4ff..0000000000000000000000000000000000000000
--- a/back/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# Back Features
-
-## Login
-To start your game, you must authenticate on the server back.
-When you are authenticated, the back server return token and room starting.
-```
-POST => /login 
-Params : 
-    email: email of user.
-```
-
-## Join a room
-When a user is connected, the user can join a room.
-So you must send emit `join-room` with information user:
-```
-Socket.io => 'join-room'
-
-    userId: user id of gamer
-    roomId: room id when user enter in game
-    position: {
-        x: position x on map
-        y: position y on map
-    }
-```
-All data users are stocked on socket client.
-
-## Send position user
-When user move on the map, you can share new position on back with event  `user-position`.
-The information sent:
-```
-Socket.io => 'user-position'
-
-    userId: user id of gamer
-    roomId: room id when user enter in game
-    position: {
-        x: position x on map
-        y: position y on map
-    }
-```
-All data users are updated on socket client.
-
-## Receive positions of all users
-The application sends position of all users in each room in every few 10 milliseconds.
-The data will pushed on event `user-position`:
-```
-Socket.io => 'user-position'
-
-    [
-        {
-            userId: user id of gamer
-            roomId: room id when user enter in game
-            position: {
-                x: position x on map
-                y: position y on map
-            }
-        },
-        ...
-    ]
-```
-
-[<<< back](../README.md) 
\ No newline at end of file
diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts
index 2cbfbf2e63ae50c53c42d2ffe16fe4ba5aff968c..95a705fa087612a853a20ad7d53d208596116807 100644
--- a/back/src/Enum/EnvironmentVariable.ts
+++ b/back/src/Enum/EnvironmentVariable.ts
@@ -1,4 +1,3 @@
-const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
 const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
 const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
 const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
@@ -14,7 +13,6 @@ const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
 export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed
 
 export {
-    SECRET_KEY,
     MINIMUM_DISTANCE,
     ADMIN_API_URL,
     ADMIN_API_TOKEN,
diff --git a/contrib/docker/.env.prod.template b/contrib/docker/.env.prod.template
new file mode 100644
index 0000000000000000000000000000000000000000..c0c10181985e2c42f697449babca890f23c162fa
--- /dev/null
+++ b/contrib/docker/.env.prod.template
@@ -0,0 +1,20 @@
+# The base domain
+DOMAIN=workadventure.localhost
+
+DEBUG_MODE=false
+JITSI_URL=meet.jit.si
+# If your Jitsi environment has authentication set up, you MUST set JITSI_PRIVATE_MODE to "true" and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret
+JITSI_PRIVATE_MODE=false
+JITSI_ISS=
+SECRET_JITSI_KEY=
+
+# URL of the TURN server (needed to "punch a hole" through some networks for P2P connections)
+TURN_SERVER=
+TURN_USER=
+TURN_PASSWORD=
+
+# The URL used by default, in the form: "/_/global/map/url.json"
+START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json
+
+# The email address used by Let's encrypt to send renewal warnings (compulsory)
+ACME_EMAIL=
diff --git a/contrib/docker/docker-compose.prod.yaml b/contrib/docker/docker-compose.prod.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..22860748c690eecefcc39ae3b878efc2cd9d4ca2
--- /dev/null
+++ b/contrib/docker/docker-compose.prod.yaml
@@ -0,0 +1,100 @@
+version: "3.3"
+services:
+  reverse-proxy:
+    image: traefik:v2.3
+    command:
+      - --log.level=WARN
+      #- --api.insecure=true
+      - --providers.docker
+      - --entryPoints.web.address=:80
+      - --entrypoints.web.http.redirections.entryPoint.to=websecure
+      - --entrypoints.web.http.redirections.entryPoint.scheme=https
+      - --entryPoints.websecure.address=:443
+      - --certificatesresolvers.myresolver.acme.email=d.negrier@thecodingmachine.com
+      - --certificatesresolvers.myresolver.acme.storage=/acme.json
+      # used during the challenge
+      - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
+    ports:
+      - "80:80"
+      - "443:443"
+      # The Web UI (enabled by --api.insecure=true)
+      #- "8080:8080"
+    depends_on:
+      - pusher
+      - front
+    volumes:
+      - /var/run/docker.sock:/var/run/docker.sock
+      - ./acme.json:/acme.json
+    restart: unless-stopped
+
+
+  front:
+    build:
+      context: ../..
+      dockerfile: front/Dockerfile
+    #image: thecodingmachine/workadventure-front:master
+    environment:
+      DEBUG_MODE: "$DEBUG_MODE"
+      JITSI_URL: $JITSI_URL
+      JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
+      API_URL: pusher.${DOMAIN}
+      TURN_SERVER: "${TURN_SERVER}"
+      TURN_USER: "${TURN_USER}"
+      TURN_PASSWORD: "${TURN_PASSWORD}"
+      START_ROOM_URL: "${START_ROOM_URL}"
+    labels:
+      - "traefik.http.routers.front.rule=Host(`play.${DOMAIN}`)"
+      - "traefik.http.routers.front.entryPoints=web,traefik"
+      - "traefik.http.services.front.loadbalancer.server.port=80"
+      - "traefik.http.routers.front-ssl.rule=Host(`play.${DOMAIN}`)"
+      - "traefik.http.routers.front-ssl.entryPoints=websecure"
+      - "traefik.http.routers.front-ssl.tls=true"
+      - "traefik.http.routers.front-ssl.service=front"
+      - "traefik.http.routers.front-ssl.tls.certresolver=myresolver"
+    restart: unless-stopped
+
+  pusher:
+    build:
+      context: ../..
+      dockerfile: pusher/Dockerfile
+    #image: thecodingmachine/workadventure-pusher:master
+    command: yarn run runprod
+    environment:
+      SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
+      SECRET_KEY: yourSecretKey
+      API_URL: back:50051
+      JITSI_URL: $JITSI_URL
+      JITSI_ISS: $JITSI_ISS
+    labels:
+      - "traefik.http.routers.pusher.rule=Host(`pusher.${DOMAIN}`)"
+      - "traefik.http.routers.pusher.entryPoints=web,traefik"
+      - "traefik.http.services.pusher.loadbalancer.server.port=8080"
+      - "traefik.http.routers.pusher-ssl.rule=Host(`pusher.${DOMAIN}`)"
+      - "traefik.http.routers.pusher-ssl.entryPoints=websecure"
+      - "traefik.http.routers.pusher-ssl.tls=true"
+      - "traefik.http.routers.pusher-ssl.service=pusher"
+      - "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver"
+    restart: unless-stopped
+
+  back:
+    build:
+      context: ../..
+      dockerfile: back/Dockerfile
+    #image: thecodingmachine/workadventure-back:master
+    command: yarn run runprod
+    environment:
+      SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
+      ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
+      ADMIN_API_URL: "$ADMIN_API_URL"
+      JITSI_URL: $JITSI_URL
+      JITSI_ISS: $JITSI_ISS
+    labels:
+      - "traefik.http.routers.back.rule=Host(`api.${DOMAIN}`)"
+      - "traefik.http.routers.back.entryPoints=web"
+      - "traefik.http.services.back.loadbalancer.server.port=8080"
+      - "traefik.http.routers.back-ssl.rule=Host(`api.${DOMAIN}`)"
+      - "traefik.http.routers.back-ssl.entryPoints=websecure"
+      - "traefik.http.routers.back-ssl.tls=true"
+      - "traefik.http.routers.back-ssl.service=back"
+      - "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
+    restart: unless-stopped
diff --git a/front/dist/index.html.tmpl b/front/dist/index.tmpl.html
similarity index 100%
rename from front/dist/index.html.tmpl
rename to front/dist/index.tmpl.html
diff --git a/front/templater.sh b/front/templater.sh
index e63617c5d1550543a72a00511de1f7fc36054a18..1851cdc5f1201297b30b4b85ffa6f6079b5b472d 100755
--- a/front/templater.sh
+++ b/front/templater.sh
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 set -x
 set -o nounset errexit
-template_file_index=dist/index.html.tmpl
+template_file_index=dist/index.tmpl.html
 generated_file_index=dist/index.html
 tmp_trackcodefile=/tmp/trackcode
 
diff --git a/front/webpack.config.js b/front/webpack.config.js
index 69d507f28c259e9420d19293b914fda6389878a5..170466efdf2356d2c21b48c9f4928d4b3f5e15b1 100644
--- a/front/webpack.config.js
+++ b/front/webpack.config.js
@@ -39,7 +39,16 @@ module.exports = {
     plugins: [
         new HtmlWebpackPlugin(
             {
-                template: './dist/index.html'
+                template: './dist/index.tmpl.html',
+                minify: {
+                    collapseWhitespace: true,
+                    keepClosingSlash: true,
+                    removeComments: false,
+                    removeRedundantAttributes: true,
+                    removeScriptTypeAttributes: true,
+                    removeStyleLinkTypeAttributes: true,
+                    useShortDoctype: true
+                }
             }
         ),
         new webpack.ProvidePlugin({
diff --git a/maps/.dockerignore b/maps/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..64d1025d1ae38208b2e5a61357affd9f7b6bdd4e
--- /dev/null
+++ b/maps/.dockerignore
@@ -0,0 +1,4 @@
+/node_modules/
+/dist/bundle.js
+/yarn-error.log
+/Dockerfile
diff --git a/messages/.dockerignore b/messages/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..64d1025d1ae38208b2e5a61357affd9f7b6bdd4e
--- /dev/null
+++ b/messages/.dockerignore
@@ -0,0 +1,4 @@
+/node_modules/
+/dist/bundle.js
+/yarn-error.log
+/Dockerfile
diff --git a/pusher/Dockerfile b/pusher/Dockerfile
index 42de3883d98d6ce3c5d31f77ea6feefaa3853e06..4aec97481efb1c0ef98def9bd9ca6bd4624cf6fa 100644
--- a/pusher/Dockerfile
+++ b/pusher/Dockerfile
@@ -1,15 +1,26 @@
-FROM thecodingmachine/workadventure-back-base:latest as builder
-WORKDIR /var/www/messages
-COPY --chown=docker:docker messages .
+# protobuf build
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
+WORKDIR /usr/src
+COPY messages .
 RUN yarn install && yarn proto
 
-FROM thecodingmachine/nodejs:12
-
-COPY --chown=docker:docker pusher .
-COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
+# typescript build
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
+WORKDIR /usr/src
+COPY pusher/yarn.lock pusher/package.json ./
 RUN yarn install
-
+COPY pusher .
+COPY --from=builder /usr/src/generated src/Messages/generated
 ENV NODE_ENV=production
 RUN yarn run tsc
 
+# final production image
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
+WORKDIR /usr/src
+COPY pusher/yarn.lock pusher/package.json ./
+COPY --from=builder2 /usr/src/dist /usr/src/dist
+ENV NODE_ENV=production
+RUN yarn install --production
+
+USER node
 CMD ["yarn", "run", "runprod"]
diff --git a/pusher/Dockerfile.prod b/pusher/Dockerfile.prod
new file mode 100644
index 0000000000000000000000000000000000000000..772532f03193a70e8e7fba241c2776f6c123897c
--- /dev/null
+++ b/pusher/Dockerfile.prod
@@ -0,0 +1,17 @@
+FROM node:12.19.0-slim
+
+RUN mkdir -p /home/node/app && chown -R node:node /home/node/app
+WORKDIR /home/node/app
+
+USER node
+ENV NODE_ENV=production
+ENV DEBUG=*
+
+COPY --chown=node:node package.json yarn.lock ./
+
+RUN yarn install --prod --frozen-lockfile
+
+COPY --chown=node:node ./dist/ ./dist/
+
+EXPOSE 8080
+CMD ["yarn", "run", "runprod"]
diff --git a/uploader/Dockerfile b/uploader/Dockerfile
index 3c471f6caf773c42d63f9f369c43a72b478900a3..e43663085da944e14c547c244484722090e33e08 100644
--- a/uploader/Dockerfile
+++ b/uploader/Dockerfile
@@ -1,9 +1,19 @@
-FROM thecodingmachine/nodejs:12
-
-COPY --chown=docker:docker uploader .
+# typescript build
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
+WORKDIR /usr/src
+COPY uploader/yarn.lock uploader/package.json ./
 RUN yarn install
-
+COPY uploader .
 ENV NODE_ENV=production
 RUN yarn run tsc
 
+# final production image
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
+WORKDIR /usr/src
+COPY uploader/yarn.lock uploader/package.json ./
+COPY --from=builder2 /usr/src/dist /usr/src/dist
+ENV NODE_ENV=production
+RUN yarn install --production
+
+USER node
 CMD ["yarn", "run", "runprod"]
diff --git a/website/.dockerignore b/website/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..576c21a2e874e01f5abb3944cfd575b83de42605
--- /dev/null
+++ b/website/.dockerignore
@@ -0,0 +1,5 @@
+/dist/
+/node_modules/
+/dist/bundle.js
+/yarn-error.log
+/Dockerfile