diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..dcf0149 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,79 @@ +name: Docker + +on: + push: + # Publish `master` as Docker `latest` image. + branches: + - master + + # Publish `v1.2.3` tags as releases. + tags: + - '*' + + # Run tests for any PRs. + pull_request: + +jobs: + # Run tests. + # See also https://docs.docker.com/docker-hub/builds/automated-testing/ + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Run tests + run: docker build . --file Dockerfile + + # Push image to GitHub Packages. + # See also https://docs.docker.com/docker-hub/builds/ + push: + # Ensure test job passes before pushing image. + needs: test + + runs-on: ubuntu-latest + if: github.event_name == 'push' + + steps: + - uses: actions/checkout@v2 + + - name: Login to ghcr.io + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Compute tag + id: compute-tag + run: | + IMAGE_ID=ghcr.io/${{ github.repository }} + + # Change all uppercase to lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + + # Strip "v" prefix from tag name + [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') + + # Use Docker `latest` tag convention + [ "$VERSION" == "master" ] && VERSION=latest + + echo IMAGE_ID=$IMAGE_ID + echo VERSION=$VERSION + + echo "DOCKER_TAG=$IMAGE_ID:$VERSION" >> "$GITHUB_OUTPUT" + + - name: Push multi-arch image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.compute-tag.outputs.DOCKER_TAG }} + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f594d0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1 +ARG NODE_VERSION=18.0.0 + +FROM node:${NODE_VERSION}-alpine + +# Use production node environment by default. +ENV NODE_ENV=production + + +WORKDIR /usr/src/app + +# Download dependencies as a separate step to take advantage of Docker's caching. +# Leverage a cache mount to /root/.npm to speed up subsequent builds. +# Leverage a bind mounts to package.json and package-lock.json to avoid having to copy them into +# into this layer. +RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=package-lock.json,target=package-lock.json \ + --mount=type=cache,target=/root/.npm \ + npm ci --omit=dev + +# Run the application as a non-root user. +USER node + +# Copy the rest of the source files into the image. +COPY . . + +# Expose the port that the application listens on. +EXPOSE 3000 + +# Run the application. +CMD ["node", "src/index.js"] diff --git a/README.md b/README.md index 25b5281..9a95f7f 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,19 @@ The key server uses [nodemailer](https://nodemailer.com) to send out emails upon For production you should use a service like [Amazon SES](https://aws.amazon.com/ses/), [Mailgun](https://www.mailgun.com/) or [Sendgrid](https://sendgrid.com/use-cases/transactional-email/). Nodemailer supports all of these out of the box. +### Docker compose + +Docker images are built from this repository and available at ghcr. You can use the sample docker-compose.yml - review it and populate an .env file with the required [settings](#Settings) before running the server. To create the database automatically, the following parameters are needed in .env file: + +``` +MONGO_URI=mongodb:27017/keyserver_db +MONGO_USER=keyserver +MONGO_PASS=somepassword +MONGO_INITDB_DATABASE=keyserver_db +``` + +The sample docker-compose.yml also contains common traefik settings, but you may need to adjust them for your own reverse proxy. + ## Run tests ```shell diff --git a/config/mongo-init.js b/config/mongo-init.js new file mode 100644 index 0000000..31733a4 --- /dev/null +++ b/config/mongo-init.js @@ -0,0 +1,12 @@ +db.createUser( + { + user: process.env.MONGO_USER, + pwd: process.env.MONGO_PASS, + roles: [ + { + role: "readWrite", + db: process.env.MONGO_INITDB_DATABASE + } + ] + } +); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6437de7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +--- +services: + mongodb: + image: mongo:7 + restart: always + volumes: + - ./data/db:/data/db + - ./config/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro + networks: + - backend + env_file: .env + security_opt: + - apparmor=unconfined + + keyserver: + image: ghcr.io/mailvelope/keyserver:latest + restart: always + depends_on: + - mongodb + networks: + - traefik-proxy + - backend + env_file: .env + security_opt: + - apparmor=unconfined + labels: + - "traefik.enable=true" + - "traefik.http.routers.keyserver.rule=Host(`keyserver.example.com`)" + - "traefik.http.routers.keyserver.entrypoints=websecure" + - "traefik.http.routers.keyserver.tls.certresolver=acme-le" + - "traefik.http.services.keyserver.loadbalancer.server.port=3000" + +networks: + backend: + traefik-proxy: diff --git a/env.sample b/env.sample new file mode 100644 index 0000000..c0bdb75 --- /dev/null +++ b/env.sample @@ -0,0 +1,22 @@ +NODE_ENV=production +LOG_LEVEL=debug +PORT=3000 +PAPERTRAIL_HOST='' +PAPERTRAIL_PORT='' +MONGO_URI=mongodb:27017/keyserver_db +MONGO_USER=keyserver +MONGO_PASS=changeme +MONGO_INITDB_DATABASE=keyserver_db +SENDER_NAME=keyserver +SENDER_EMAIL=changeme +SMTP_HOST=changeme +SMTP_PORT=587 +SMTP_TLS=false +SMTP_STARTTLS=true +SMTP_PGP='' +SMTP_USER='' +SMTP_PASS='' +HTTPS_UPGRADE=true +HTTPS_KEY_PIN='' +HTTPS_KEY_PIN_BACKUP='' +PUBLIC_KEY_PURGE_TIME=30