Plainionist Become a Better Developer, Deliver Software Faster

Running a PWA in an intranet

In the previous article I summarized how I used Docker to host a web application on my Raspberry PI. This successful experiment inspired me to start a new one: Let’s turn this “regular” web application into a Progressive Web App (PWA), only hosted and operating in my local network.

It turned out this experiment was a lot trickier than initially thought …

After some research I was convinced that enabling PWA for an existing web application should actually be quite simple. As my web application is based on Vue.js and Vite I basically followed these articles


Add support for PWA to the Web UI

So I ran

vue add pwa

in the root of the Web UI project of the web application which successfully added the service worker.

Then I installed vite support for PWA

pnpm i vite-plugin-pwa -D

and configured the information for the manifest in the vite.config.js

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  build: {
    outDir: '../WebApi/dist/public/'
  plugins: [
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'apple-touch-icon-180x180.png', 'logo.png'],
      manifest: {
        name: 'Price Watch',
        short_name: 'Price Watch',
        description: 'Watch list for crypto and stock prices',
        theme_color: '#1e1e1e',
        icons: [
            src: 'pwa-64x64.png',
            sizes: '64x64',
            type: 'image/png'
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png'
            src: 'maskable-icon-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable'
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))

Finally I installed vite’s assets generator


and added the following small script to the package.json of the web UI

  "scripts": {
    "generate-pwa-assets": "pwa-assets-generator --preset minimal-2023 public/logo.png"

to generate the minimal images and icons from just a single logo


As a PWA requires a secure connection I added HTTPS support to backend, which would host the Web UI in the production setup.

const express = require('express')
const https = require('https')

const configHome = process.env.CONFIG
const key = fs.readFileSync(`${configHome}/selfsigned.key`);
const cert = fs.readFileSync(`${configHome}/selfsigned.crt`);

const app = express()
const server = https.createServer({key: key, cert: cert }, app);

const port = 8001
const httpsPort = 8002


app.listen(port, () => {
  console.log(`Listening at http://localhost:${port}`)

server.listen(httpsPort, function () {
  console.log(`Listening at https://localhost:${httpsPort}`)

SSL Certificates

So far so easy. The real challenge was to setup the SSL certificates.

I tried using “self signed” certificates as described in this article but the browser complains that these certificates are not trusted.

I then learned about Let’s Encrypt but this doesn’t seem to work in a private network.

I finally learned that I need to be come my own “Certificate Authority” to make the browser stop complaining.

Therefore I followed this article:

After having completed these steps i used the “CX file explorer” App on my smartphone (Android) to copy over the myCA.pem and install it on my device

Security & location > Encryption & credentials > Install a certificate > CA certificate


The last necessary step was to change the Docker call to export the HTTPS port and also provide access to the SSL certificates which I copied to /home/me/price-watch on my PI

docker run -d --restart=unless-stopped --name price-watch -p 8001:8001 -p 8002:8002 -v /home/me/price-watch:/app/config -e "CONFIG=/app/config" price-watch:pi

When I then opened the web application on my smartphone via the HTTPS port my browser offered me to install the application. The installation went smooth and since then I can use my web application as if it would be a “native app” on my smartphone.

Tags: technologies