How to deploy a Deno app with data from Deno KV and JSON files?

Background

My app was built specifically for Deno Deploy so it’s tightly coupled with Deno KV. Also, there are JSON files that are constantly modified on the local to provide another set of data. Before Deno Deploy Classic was closed, all I need was to update those JSON files locally, git push --force the whole project to GitHub and the data from JSON would be updated, while the data from the KV was unchanged. I didn’t have to care about Docker and database, and didn’t think I would need to understand them.

Now Deno Deploy requires me to update the app to Deno 2, which looks not trivial to me, as my app depends on some old packages. As I intend to rewrite it in a different language anyway, I want to keep the app as is, with little modification as possible. However I’m willing to learn new DevOp knowledge.

Problem

Is it possible to achieve that workflow in Fly? It seems to me that I have to launch the app and the database separately. Is it possible to launch both as one, especially when git push --force is an easy way for me to update the JSON files. I think with doing the volume right this can be done, as Deno KV is just an sqlite3 file. But I’m not sure how to config the fly.toml file.

Currently here is how I setup:

dockerfile

FROM denoland/deno:1.46.3 AS builder

RUN apt-get update && apt-get install -y procps && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY deno.json ./
COPY . .

EXPOSE 8000
CMD ["deno", "task", "start"]

docker-compose.yaml

services:
  main:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - ./Cấu hình và dữ liệu:/app/Cấu hình và dữ liệu
    environment:
      - DENO_DIR=/app/.deno_dir
    restart: unless-stopped

volumes:
  deno_kv:
    driver: local

fly.toml

app = "doi-thoai"
build = { }
primary_region = "sin"

[env]
CORS_API = "https://doi-thoai.fly.dev"
DATABASE_URL = "https://denokv-test.fly.dev"
DENO_KV_ACCESS_TOKEN = "xxxxxxxxxxx"
ORIGIN = "http://doi-thoai.fly.dev"

[http_service]
auto_start_machines = true
auto_stop_machines = true
force_https = true
internal_port = 8_000
min_machines_running = 0
processes = [ "app" ]

[[vm]]
cpu_kind = "shared"
cpus = 1
memory = "1gb"
memory_mb = 1_024

When I run the container on my local, it looks working. But on Fly the app only shows up the UI but no data. The log says:

Machines are not listening on 0.0.0.0:8000 so our proxy is having a hard time finding a candidate to route requests to. This is an issue with your application code.

  • Your app could be missing environment variables. Some frameworks require specific environment variables to be set otherwise they stop your app.
  • The app could be listening to the wrong port, you can either change your fly.toml internal_port or modify your application code to listen on the correct port.
  • Make sure your app is listening to 0.0.0.0 and not localhost or 127.0.0.1.
  • Your app could not be listening at all, check your logs to make sure it’s running correctly.

Checking the browser console it says:

Loading module from “https://doi-thoai.fly.dev/_frsh/js/5515824eaf1af6b89dfc73010a29137508b5de18/chunk-2EHLMYDL.js” was blocked because of a disallowed MIME type (“text/html”).

It also looks like that the deps are only downloaded on the first load, making it’s super slow.

I’m not sure how to fix this. In theory, even when the database is failed, the JSON data should still work, as it is a part of the code.

If it works locally, it should work remotely.

A quick comment on your local config - ./Cấu hình và dữ liệu:/app/Cấu hình và dữ liệu is asking for trouble from a path perspective. Remove spaces and UTF-characters, e.g. something like ./config-data:/app/config-data.

From there you can configure your app to look into /app/config-data/ plus whatever files/subfolders you need. You can also configure a Fly volume to map to /app/config-data so that, instead of writing to this ephemeral path in your throwaway machine, the volume catches it instead.

Your fault report and error seem to be in conflict - you say that the UI is operational, but you are getting a proxy error saying that port 8000 is not responding in your app. Is your UI not being served from port 8000? I wonder if you are getting that error because your listener is slow to start, but it does start serving requests eventually. If so, your app should save data already, though if you don’t have a Fly volume, the data will be lost when you redeploy.

This is a web server problem that your browser is highlighting for you. This file should have a response Content-Type header of application/js. I am not familiar with Deno, but it should be configured in your listener or your web server.

It is not impossible that the Fly proxy is breaking this though. Could you find the same file in a local run of the app to see what the same header is on the same file?

Well, as you said, if it works locally, it should work remotely. But anyway, I’ve changed the path to your suggestion. Nothing changes.

I haven’t set Fly volume yet. Currently I just want to make sure that it works.

Is your UI not being served from port 8000?

How can I check? Also, what is my listener? My browser?

Could you find the same file in a local run of the app to see what the same header is on the same file?

In my local, there is no folder _frsh, only _fresh. I don’t know why the letter e is dropped. Even assuming that is fine, then that folder is dockerignore’d. So it’s generated only on Fly’s machine.

My wording was a bit careless. I should have said: if it doesn’t work locally, it definitely won’t work remotely. There are a few additional bits that need to work for the Fly bit to work.

That said, your Docker Compose YAML won’t affect Fly - by default, Fly doesn’t use Docker.

That is OK for now. You won’t have any data persistence remotely, but getting it to work at all is a good first step.

It does not look like you have declared any static folders that are served directly by the proxy, so unless you are using a frontend proxy like Cloudflare, I would guess that your UI is indeed coming from your machine on port 8000.

No, a listener is another name for a web/API server. I assume you are using one built into Deno.

I think I can see the problem(s):

The first three JS files are loaded in parallel, but they take 10 seconds; that is bad enough. Then the body is loaded, which is fully 10MB in size, which is a liability. The favicon is nearly half a megabyte!

I would say I didn’t get anything rendered for 60 seconds, which likely explains why the proxy thinks the app is not operational; it is kinda up, but it is like a snail on sleeping pills. :snail:

All the items in red are missing or corrupted. At a guess, the app doesn’t have enough memory, but as previously discussed, the server response headers are wrongly labelling the type of JS chunks too. I would be curious to know why there are so many of them.

In summary, I hope this is a personal vibe-coding effort, and not something you paid for. While I don’t know Deno, I would be surprised if this was not a mess under the covers - from the outside, I would not expect this to scale at all.

This is probably the cause of the 404s. I don’t know if your app will work if you fix it, but it would be a good place to start. Would you share where the string “_fresh” is configured? Make sure, again, that the “e” is standard ASCII and not some accented or umlauted variant.

Update

_frsh is fine here. My research indicates that this string is correct. The app is using a web framework called Deno Fresh.

Ah, interestingly some _frsh resources do actually succeed, and they have the right type:

https://doi-thoai.fly.dev/_frsh/js/90ca1dff35d26ec5e4b9700e27991c8036ed049d/chunk-V2DJWQXH.js

So the wrong Content-Type is a red herring; if the resource actually existed then the internal server would resolve the type correctly. I wonder if a build process or cache warmer is failing?

Right, your Dockerfile is missing a RUN deno task build line. Add that before the EXPOSE, commit, and redeploy. (AI gave me this answer, but it seems good to me. You can always shell into your running machine and try deno task build manually if you wish).