From ec77305aba4775951060647a01aeb0d1f5aa244e Mon Sep 17 00:00:00 2001 From: Nick Sellen Date: Wed, 6 Mar 2024 13:34:07 +0000 Subject: [PATCH] Add Dockerfile (#2711) * Add Dockerfile * Add docker build github action --- .dockerignore | 6 ++ .github/workflows/docker.yml | 46 ++++++++++++++ Dockerfile | 21 +++++++ docker/nginx.conf.template | 116 +++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker.yml create mode 100644 Dockerfile create mode 100644 docker/nginx.conf.template diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..7563c6ff6e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +storybook-static +.git +.idea +Dockerfile +dist diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..38d291dd71 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,46 @@ +name: Publish Docker image + +on: + push: + branches: + - "master" + release: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Log in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..b9bc51957f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +ARG NODE_VERSION=20 +ARG NGINX_VERSION=1.25 + +FROM docker.io/node:${NODE_VERSION} as build + +COPY . /app/code + +WORKDIR /app/code + +RUN yarn +RUN yarn build + +#-------------------------------------- + +FROM docker.io/nginx:${NGINX_VERSION}-alpine + +LABEL org.opencontainers.image.source=https://github.com/karrot-dev/karrot-frontend + +COPY --from=build /app/code/dist/pwa /usr/share/nginx/html + +COPY docker/nginx.conf.template /etc/nginx/templates/default.conf.template diff --git a/docker/nginx.conf.template b/docker/nginx.conf.template new file mode 100644 index 0000000000..9bee34a8f0 --- /dev/null +++ b/docker/nginx.conf.template @@ -0,0 +1,116 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +client_max_body_size ${FILE_UPLOAD_MAX_SIZE}; + +server { + + listen 80; + listen [::]:80; + + root /usr/share/nginx/html; + + location /assets { + expires max; + } + + location /css { + expires max; + } + + location /js { + expires max; + } + + location /img { + expires max; + } + + location /fonts { + expires max; + } + + location / { + + try_files $uri /index.html; + + # security related headers + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header X-Content-Type-Options nosniff; + + # kill cache + add_header Last-Modified $date_gmt; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; connect-src 'self' https://nominatim.openstreetmap.org https://sentry.io https://*.ingest.sentry.io ${CSP_CONNECT_SRC} blob:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' https: data: blob:;"; + + if_modified_since off; + expires off; + etag off; + } + + # Special permissive policy for this page + #location /bundlesize.html { + #include /etc/nginx/snippets/{{ site }}/headers; + #{% if csp_config is defined %} + #add_header {{ csp_header }} "default-src 'self' 'unsafe-inline' data:;"; + #{% endif %} + #} + + location ~ ^\/(api(\-auth)?|docs|silk|static)\/ { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + + resolver 127.0.0.11 valid=3s; + set $backend ${BACKEND}; + proxy_pass http://$backend$request_uri; + + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Sec-WebSocket-Extensions $http_sec_websocket_extensions; + proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key; + proxy_set_header Sec-WebSocket-Protocol $http_sec_websocket_protocol; + proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version; + } + + location /media/ { + alias ${FILE_UPLOAD_DIR}; + expires max; + } + + # never serve up the tmp content + location /media/tmp/ { + deny all; + return 404; + } + + # these are served via django + location ~ /media/conversation_message_attachment_(files|previews|thumbnails)/ { + deny all; + return 404; + } + + # support for X-Accel-Redirect + location /uploads/ { + internal; + alias ${FILE_UPLOAD_DIR}; + etag on; + } + + location /community_proxy/ { + proxy_pass https://community.karrot.world/; + } + + error_page 503 @maintenance; + + location @maintenance { + try_files /maintenance.html =503; + } + +}