<p>So I've been struggling with how to deploy a dockerized application. The app consists of a react frontend, and an express API.
My <code>docker-compose.yml</code> for the development environment looks like the following:</p>

<pre><code>version: '3'
services:
  # Express Container
  backend:
    build: ./backend
    expose:
      - ${BACKEND_PORT}
    env_file:
      - ./.env
    environment:
      - PORT=${BACKEND_PORT}
    ports:
      - ${BACKEND_PORT}:${BACKEND_PORT}
    volumes:
      - ./backend:/backend
    command: npm run devstart
    links:
      - mongo
  # React Container
  frontend:
    build: './frontend'
    expose:
      - ${REACT_APP_PORT}
    env_file:
      - ./.env
    environment:
      - REACT_APP_BACKEND_PORT=${BACKEND_PORT}
    ports:
      - ${REACT_APP_PORT}:${REACT_APP_PORT}
    volumes: 
      - ./frontend/src:/frontend/src
      - ./frontend/public:/frontend/public
    links:
      - backend
    command: npm start
  mongo:
    image: mongo
    ports:
      - "27017:27017"
</code></pre>

<p>But I've been struggling on how to structure it for production.</p>

<p>I've seen that there is basically 3 options:</p>

<ol>
<li>Deploy the frontend and backend separately to different servers. In this case, the react frontend would be on some web hosting service, and the express backend would be hosted on kubernetes</li>
<li>Have the express application serve out the react application</li>
<li>Have the applications separate but use NGINX to proxy API requests to the express app</li>
</ol>

<p>I was thinking I would go with option 3, because it would keep development and production environments quite similar. (Please tell me if this is bad structure, this application is expected to receive a lot of traffic.)</p>

<p>Should I maybe forget docker-compose and create a multistage dockerfile that uses multistage builds to copy over frontend and backend code? That way I can deploy a single Docker container?</p>

<p>My folder structure looks like the following:</p>

<pre><code>app/
  .env
  docker-compose.yml
  docker-compose.prod.yml
  .gitignore
  frontend/
    Dockerfile
    ... react stuff
  backend
    Dockerfile
    .. express stuff
</code></pre>

<p>Am I going about this all wrong? How have you deployed your applications with docker-compose to production (preferably on kubernetes).</p>

<p>I can find tons of stuff about how to get this stuff running in development, but I'm lost when it comes to direction for deploying this type of stack.</p>

Matthew Weeks
<p>You might start with reading through the <a href="https://kubernetes.io/docs/home/" rel="nofollow noreferrer">Kubernetes documentation</a> and understanding what's straightforward and what's not.  You're most interested in Deployments and Services, and possibly Ingress.  The MongoDB setup with associated persistent state will be more complicated, and you might look at a prepackaged solution like <a href="https://github.com/helm/charts/tree/master/stable/mongodb" rel="nofollow noreferrer">the stable/mongodb Helm chart</a> or <a href="https://github.com/mongodb/mongodb-enterprise-kubernetes" rel="nofollow noreferrer">MongoDB's official operator</a>.</p>

<p>Note that an important part of the Kubernetes setup is that there will almost always be multiple Nodes, and you don't get a whole lot of control over which Node a Pod will be placed on.  In particular that means that the Docker Compose <code>volumes:</code> you show won't work well in a Kubernetes environment – in addition to doing all the normal Kubernetes deployment work, you'd also need to replicate the application source code to every node.  That's twice the work for the same deployment.  Usually you will want all of the application code to be contained in the Docker image, with a typical Node-based Dockerfile looking something like</p>

<pre><code>FROM node:10
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY ./ ./
RUN yarn build
EXPOSE 3000
CMD yarn start
</code></pre>

<p>Just within the <code>docker-compose.yml</code> file you show:</p>

<ul>
<li><p>The <code>volumes:</code> make your containers substantially different from what you might run in production; delete them.</p></li>
<li><p>Don't bother making the container-internal ports configurable.  In plain Docker, Docker Compose, and Kubernetes you can remap the container-internal port to an arbitrary externally-accessible port at deployment time.  You can pick fixed numbers here and it's fine.</p></li>
<li><p>Several of the details you show, like the ports the container <code>expose:</code> and the default <code>command:</code> to run, are properly parts of the image (every time you run the image they will be identical), so move these into the Dockerfile.</p></li>
<li><p><code>links:</code> are redundant these days, and you can just delete them.  In Docker Compose you can always reach the name of another service by the name of its service block.</p></li>
<li><p>The names of the other related services will be different in different environments.  For example, MongoDB might be on <code>localhost</code> when you're actually developing your application outside of Docker, <code>mongo</code> in the configuration you show, <code>mongo.myapp.svc.cluster.local</code> in Kubernetes, or you might choose to run it outside of Docker entirely.  You'll generally want these to be configurable, usually with environment variables.</p></li>
</ul>

<p>This gives you a <code>docker-compose.yml</code> file a little more like:</p>

<pre><code>version: '3'
services:
  backend:
    build: ./backend
    environment:
      - MONGO_URL: 'mongo://mongo'
    ports:
      - 3000:3000
  frontend:
    build: './frontend'
    environment:
      - BACKEND_URL: 'http://backend'
    ports:
      - 8000:8000
  mongo:
    image: mongo
    ports:
      - "27017:27017"
</code></pre>

<p>As @frankd hinted in their answers, it's also very common to use a tool like Webpack to precompile a React application down into a set of static files.  Depending on how you're actually deploying this it could make sense to run this compilation step ahead of time and push those compiled Javascript and CSS files out to some other static-hosting service, and take it out of Docker/Kubernetes entirely.</p>
