-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add official Docker image and optional CORS filter
Adapts development Dockerfile from @roje-bodc and @MattHopsonNOC and Axiom Dockerfile to a new official ERDDAP Docker image. Also adds an optional internally managed CORS filter (enabled by setting `enableCors=true` in settings). CORS filter behavior can be modified using environment variables CORS_ALLOW_HEADERS and CORS_ALLOW_ORIGIN.
- Loading branch information
1 parent
625c619
commit 87e8491
Showing
16 changed files
with
529 additions
and
468 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# ERDDAP™ Docker Image | ||
|
||
The Dockerfile included in this project builds the offical ERDDAP™ Docker image. | ||
The Dockerfile uses [Apache Maven](https://maven.apache.org/) to package the application into a WAR file, | ||
and serves the application using [Apache Tomcat](https://tomcat.apache.org/). | ||
|
||
By default the local ERDDAP source code is used to build the image, but arbitrary git | ||
repositories and branches can alternately be used in the build. | ||
|
||
## Building the image | ||
|
||
To build the docker image you can run the following command from the root of the ERDDAP™ project: | ||
|
||
```bash | ||
docker build -t erddap-docker . | ||
``` | ||
|
||
The initial build of ERDDAP™ may take a fair amount of time, but the Dockerfile uses cache mounts | ||
in order to speed up subsequent builds of the application by caching dependencies. | ||
It is worth noting that the ERDDAP™ unit tests are ran as part of the build stage, while | ||
integration tests are skipped. | ||
|
||
### Building from git | ||
|
||
To build an image with source code from a specific git repository and branch instead of the local | ||
source, set build arguments `BUILD_FROM_GIT=1`, `ERDDAP_GIT_URL=<url_to_repo>`, | ||
and `ERDDAP_GIT_BRANCH=<tag_or_branch>`. If `ERDDAP_GIT_BRANCH` is not a tag and is a branch | ||
whose contents can change over time, `ERDDAP_GIT_CACHE_BUST` should also be set to a unique value | ||
to force Docker to not cache a previous build layer and instead fetch and build the source. | ||
|
||
Example: | ||
|
||
``` | ||
docker build --build-arg BUILD_FROM_GIT=1 \ | ||
--build-arg ERDDAP_GIT_URL=https://github.com/someuser/erddap \ | ||
--build-arg ERDDAP_GIT_BRANCH=experimental-feature-3 \ | ||
--build-arg ERDDAP_GIT_CACHE_BUST=$(date +%s) \ | ||
-t erddap-docker:experimental-feature-3 . | ||
``` | ||
|
||
## Running the image | ||
Once the image has been built, the following command can be used run an ERDDAP™ container: | ||
|
||
```bash | ||
docker run -p 8080:8080 erddap-docker | ||
``` | ||
|
||
The `--detach` or `-d` flag can be added to detach this process from your terminal. | ||
|
||
ERDDAP™ will then be accessible at the URL `http://localhost:8080/erddap`. | ||
|
||
## Running with Docker Compose | ||
|
||
An example Docker Compose stack is provided in `docker-compose.yml`. This stack will | ||
serve the default ERDDAP™ demonstration datasets unless a `datasets.xml` file is | ||
mounted as a volume to `/usr/local/tomcat/content/erddap/datasets.xml`. | ||
|
||
To build or rebuild the image: | ||
|
||
``` | ||
docker compose build | ||
``` | ||
|
||
To run the stack: | ||
|
||
``` | ||
docker compose up -d | ||
``` | ||
|
||
An ERDDAP™ instance should then be available at <http://localhost:8080>. | ||
|
||
To view and tail Tomcat and ERDDAP™ logs: | ||
|
||
``` | ||
docker compose logs -f | ||
``` | ||
|
||
To shut down the stack: | ||
|
||
``` | ||
docker compose down | ||
``` | ||
|
||
Many options can be customized by setting environment variables (`ERDDAP_PORT` etc). | ||
See the `docker-compose.yml` file for details. | ||
|
||
## Config | ||
|
||
By default generic setup values are set in the Docker image. You can and should customize those values | ||
using [environment variables](https://github.com/ERDDAP/erddap/blob/main/DEPLOY_INSTALL.md#setupEnvironmentVariables) | ||
and/or a custom `setup.xml` file mounted to `/usr/local/tomcat/content/erddap/setup.xml` | ||
|
||
For example, to set the ERDDAP™ base URL, set environment variable `ERDDAP_baseUrl=http://yourhost:8080` | ||
on the Docker container. | ||
|
||
``` | ||
docker run -p 8080:8080 -e ERDDAP_baseUrl=http://yourhost:8080` erddap-docker | ||
``` | ||
|
||
Similarly, the default ERDDAP™ demonstration datasets will be served unless a custom `datasets.xml` | ||
file is mounted as a volume to `/usr/local/tomcat/content/erddap/datasets.xml`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# Build the ERDDAP war from source | ||
FROM maven:3.9.6-eclipse-temurin-21 AS build | ||
|
||
# install zip so certain tests can pass | ||
RUN apt-get update && \ | ||
apt-get install -y --no-install-recommends git zip | ||
|
||
WORKDIR /app/ | ||
|
||
# Copy in source files and build the war file. | ||
COPY development ./development | ||
COPY download ./download | ||
COPY images ./images | ||
COPY src ./src | ||
COPY WEB-INF ./WEB-INF | ||
COPY .mvn ./.mvn | ||
COPY pom.xml . | ||
|
||
# if BUILD_FROM_GIT == 1, use code from git clone instead of local source | ||
ARG BUILD_FROM_GIT=0 | ||
ARG ERDDAP_GIT_URL=https://github.com/ERDDAP/erddap.git | ||
ARG ERDDAP_GIT_BRANCH=main | ||
ARG ERDDAP_GIT_CACHE_BUST=1 | ||
RUN if [ "$BUILD_FROM_GIT" = "1" ] && [ -n "$ERDDAP_GIT_URL" ] && [ -n "$ERDDAP_GIT_BRANCH" ]; then \ | ||
find . -mindepth 1 -delete; \ | ||
git clone ${ERDDAP_GIT_URL} --depth 1 --branch ${ERDDAP_GIT_BRANCH} .; \ | ||
fi | ||
|
||
ARG SKIP_TESTS=false | ||
RUN --mount=type=cache,id=m2_repo,target=/root/.m2/repository \ | ||
mvn --batch-mode -DskipTests=${SKIP_TESTS} -Dgcf.skipInstallHooks=true \ | ||
-Ddownload.unpack=true -Ddownload.unpackWhenChanged=false \ | ||
-Dmaven.test.redirectTestOutputToFile=true package \ | ||
&& find target -maxdepth 1 -type d -name 'ERDDAP-*' -exec mv {} target/ERDDAP \; | ||
|
||
# Run the built erddap war via a tomcat instance | ||
FROM tomcat:10.1.19-jdk21-temurin-jammy | ||
|
||
RUN apt-get update && apt-get install -y \ | ||
gosu \ | ||
unzip \ | ||
zip \ | ||
&& apt-get clean \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
# Remove default Tomcat web applications | ||
RUN rm -rf ${CATALINA_HOME}/webapps/* ${CATALINA_HOME}/webapps.dist | ||
|
||
COPY --from=build /app/content /usr/local/tomcat/content | ||
COPY --from=build /app/target/ERDDAP /usr/local/tomcat/webapps/erddap | ||
|
||
# Redirect root path / to /erddap | ||
RUN mkdir "${CATALINA_HOME}/webapps/ROOT" \ | ||
&& echo '<% response.sendRedirect("/erddap"); %>' > "${CATALINA_HOME}/webapps/ROOT/index.jsp" | ||
|
||
COPY ./docker/tomcat/conf/server.xml ./docker/tomcat/conf/context.xml "${CATALINA_HOME}/conf/" | ||
COPY ./docker/tomcat/bin/setenv.sh "${CATALINA_HOME}/bin/" | ||
|
||
# Set placeholder values for setup.xml | ||
ENV ERDDAP_deploymentInfo="docker" \ | ||
ERDDAP_bigParentDirectory="/erddapData" \ | ||
ERDDAP_baseUrl="http://localhost:8080" \ | ||
ERDDAP_baseHttpsUrl="https://localhost:8443" \ | ||
ERDDAP_emailEverythingTo="set-me@domain.com" \ | ||
ERDDAP_emailDailyReportsTo="set-me@domain.com" \ | ||
ERDDAP_emailFromAddress="set-me@domain.com" \ | ||
ERDDAP_emailUserName="" \ | ||
ERDDAP_emailPassword="" \ | ||
ERDDAP_emailProperties="" \ | ||
ERDDAP_emailSmtpHost="" \ | ||
ERDDAP_emailSmtpPort="" \ | ||
ERDDAP_adminInstitution="Set-me Institution" \ | ||
ERDDAP_adminInstitutionUrl="https://set-me.invalid" \ | ||
ERDDAP_adminIndividualName="Firstname Surname" \ | ||
ERDDAP_adminPosition="ERDDAP Administrator" \ | ||
ERDDAP_adminPhone="555-555-5555" \ | ||
ERDDAP_adminAddress="123 Simons Ave." \ | ||
ERDDAP_adminCity="Anywhere" \ | ||
ERDDAP_adminStateOrProvince="MD" \ | ||
ERDDAP_adminPostalCode="12345" \ | ||
ERDDAP_adminCountry="USA" \ | ||
ERDDAP_adminEmail="set-me@domain.com" | ||
|
||
ENV ERDDAP_VERSION_SUFFIX="docker" | ||
|
||
COPY ./docker/entrypoint.sh /entrypoint.sh | ||
|
||
ENTRYPOINT ["/entrypoint.sh"] | ||
EXPOSE 8080 | ||
CMD ["catalina.sh", "run"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
WEB-INF/classes/gov/noaa/pfel/erddap/http/CorsResponseFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package gov.noaa.pfel.erddap.http; | ||
|
||
import com.cohort.util.String2; | ||
import gov.noaa.pfel.erddap.util.EDStatic; | ||
import jakarta.servlet.Filter; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.ServletRequest; | ||
import jakarta.servlet.ServletResponse; | ||
import jakarta.servlet.annotation.WebFilter; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.util.Arrays; | ||
import org.apache.commons.lang3.StringUtils; | ||
|
||
/** Add CORS headers to the response if EDStatic.enableCors is true. */ | ||
@WebFilter("/*") | ||
public class CorsResponseFilter implements Filter { | ||
public static final String DEFAULT_ALLOW_HEADERS = | ||
"Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent"; | ||
|
||
@Override | ||
public void doFilter( | ||
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | ||
throws IOException, ServletException { | ||
if (EDStatic.enableCors) { | ||
HttpServletRequest request = (HttpServletRequest) servletRequest; | ||
HttpServletResponse response = (HttpServletResponse) servletResponse; | ||
String requestOrigin = StringUtils.trim(request.getHeader("Origin")); | ||
if (requestOrigin != null && requestOrigin.equalsIgnoreCase("null")) { | ||
requestOrigin = null; | ||
} | ||
|
||
if (EDStatic.corsAllowOrigin == null || EDStatic.corsAllowOrigin.length == 0) { | ||
// If corsAllowOrigin is not set, any origin is allowed | ||
if (String2.isSomething(requestOrigin)) { | ||
response.setHeader("Access-Control-Allow-Origin", requestOrigin); | ||
} else { | ||
response.setHeader("Access-Control-Allow-Origin", "*"); | ||
} | ||
} else { | ||
// If corsAllowedOrigin is set, make sure the request origin was provided and is in the | ||
// corsAllowedOrigin list | ||
if (String2.isSomething(requestOrigin)) { | ||
if (Arrays.asList(EDStatic.corsAllowOrigin).contains(requestOrigin.toLowerCase())) { | ||
response.setHeader("Access-Control-Allow-Origin", requestOrigin); | ||
} else { | ||
response.setHeader( | ||
"Access-Control-Allow-Origin", requestOrigin + ".origin-not-allowed.invalid"); | ||
} | ||
} else { | ||
response.setHeader("Access-Control-Allow-Origin", "https://origin-not-provided.invalid"); | ||
} | ||
} | ||
|
||
response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); | ||
response.setHeader("Access-Control-Allow-Headers", EDStatic.corsAllowHeaders); | ||
|
||
if (request.getMethod().equalsIgnoreCase("OPTIONS")) { | ||
response.setStatus(HttpServletResponse.SC_NO_CONTENT); | ||
response.setHeader("Access-Control-Max-Age", "7200"); | ||
return; | ||
} | ||
} | ||
filterChain.doFilter(servletRequest, servletResponse); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.