I'm trying to include the React part of a simple CRUD React/Spring-boot application into Docker. It works fine when neither side is in Docker. But when I create a docker file from the front end and run it, I get the initial login page, but after that, it's the default white label error page with a 404 code.
The network activity shows it's trying to make a call to http://localhost:808/private, which looks like it may be the redirect to the error page.
I'm thinking the problem may be that the back end needs to be in the container as well, which is the ultimate plan. But I'm not sure if that's the problem. It seems like an app in a container should be able to make an API call to the outside of the container.
Here's the nginx.conf file:
# auto detects a good number of processes to run
worker_processes auto;
#Provides the configuration file context in which the directives that affect connection processing are specified.
events {
# Sets the maximum number of simultaneous connections that can be opened by a worker process.
worker_connections 8000;
# Tells the worker to accept multiple connections at a time
multi_accept on;
}
http {
# what times to include
include /etc/nginx/mime.types;
# what is the default one
default_type application/octet-stream;
# Sets the path, format, and configuration for a buffered log write
log_format compression '$remote_addr - $remote_user [$time_local] '
'"$request" $status $upstream_addr '
'"$http_referer" "$http_user_agent"';
server {
# listen on port 80
listen 80;
# save logs here
access_log /var/log/nginx/access.log compression;
# nginx root directory
root /var/www;
# what file to server as index
index index.html index.htm;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to redirecting to index.html
try_files $uri $uri/ /index.html;
}
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
add_header Cache-Control "public";
}
# Javascript and CSS files
location ~* \.(?:css|js)$ {
try_files $uri =404;
expires 1y;
access_log off;
add_header Cache-Control "public";
}
# Any route containing a file extension (e.g. /devicesfile.js)
location ~ ^.+\..+$ {
try_files $uri =404;
}
}
}
Edit: more files
DockerFile:
#### Stage 1: Build the react application
FROM node:12.4.0-alpine as build
# Configure the main working directory inside the docker image.
# This is the base directory used in any further RUN, COPY, and ENTRYPOINT
# commands.
WORKDIR /app
# Copy the package.json as well as the package-lock.json and install
# the dependencies. This is a separate step so the dependencies
# will be cached unless changes to one of those two files
# are made.
COPY package.json package-lock.json ./
RUN yarn
# Copy the main application
COPY . ./
# Arguments
ARG REACT_APP_API_BASE_URL
ENV REACT_APP_API_BASE_URL=${REACT_APP_API_BASE_URL}
# Build the application
RUN yarn build
#### Stage 2: Serve the React application from Nginx
FROM nginx:1.17.0-alpine
# Copy the react build from Stage 1
COPY --from=build /app/build /var/www
# Copy our custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Expose port 3000 to the Docker host, so we can access it
# from the outside.
EXPOSE 80
ENTRYPOINT ["nginx","-g","daemon off;"]
App.js
import React, { Component } from 'react';
import './App.css';
import Home from './Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import LicenseList from './LicenseList';
import LicenseEdit from './LicenseEdit';
import { CookiesProvider } from 'react-cookie';
class App extends Component {
render() {
return (
<CookiesProvider>
<Router>
<Switch>
<Route path='/' exact={true} component={Home}/>
<Route path='/licenses' exact={true} component={LicenseList}/>
<Route path='/licenses/:id' component={LicenseEdit}/>
</Switch>
</Router>
</CookiesProvider>
)
}
}
export default App;
Home.js
import React, { Component } from 'react';
import './App.css';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';
import { withCookies } from 'react-cookie';
class Home extends Component {
state = {
isLoading: true,
isAuthenticated: false,
user: undefined
};
constructor(props) {
super(props);
const {cookies} = props;
this.state.csrfToken = cookies.get('XSRF-TOKEN');
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
}
async componentDidMount() {
const response = await fetch('/api/user', {credentials: 'include'});
const body = await response.text();
if (body === '') {
this.setState(({isAuthenticated: false}))
} else {
this.setState({isAuthenticated: true, user: JSON.parse(body)})
}
}
login() {
let port = (window.location.port ? ':' + window.location.port : '');
if (port === ':3000') {
port = ':8080';
}
window.location.href = '//' + window.location.hostname + port + '/private';
}
logout() {
fetch('/api/logout', {method: 'POST', credentials: 'include',
headers: {'X-XSRF-TOKEN': this.state.csrfToken}}).then(res => res.json())
.then(response => {
window.location.href = response.logoutUrl + "?id_token_hint=" +
response.idToken + "&post_logout_redirect_uri=" + window.location.origin;
});
}
render() {
const message = this.state.user ?
<h2>Welcome, {this.state.user.name}!</h2> :
<p>Please log in to manage Analytics Licenses</p>;
const button = this.state.isAuthenticated ?
<div>
<Button color="link"><Link to="/licenses">Manage Analytics Licenses</Link></Button>
<br/>
<Button color="link" onClick={this.logout}>Logout</Button>
</div> :
<Button color="primary" onClick={this.login}>Login</Button>;
return (
<div>
<AppNavbar/>
<Container fluid>
{message}
{button}
</Container>
</div>
);
}
}
export default withCookies(Home);
Any ideas/thoughts are most welcome.