···11+# flyctl launch added from client/.gitignore
22+client/**/*.beam
33+client/**/*.ez
44+client/build
55+client/**/erl_crash.dump
66+77+#Added automatically by Lustre Dev Tools
88+client/.lustre
99+client/dist
1010+client/node_modules
1111+1212+# flyctl launch added from server/.gitignore
1313+server/**/*.beam
1414+server/**/*.ez
1515+server/build
1616+server/**/erl_crash.dump
1717+server/**/*db*
1818+server/**/.env
1919+2020+# flyctl launch added from server/build/packages/squall/.gitignore
2121+server/build/packages/squall/**/*.beam
2222+server/build/packages/squall/**/*.ez
2323+server/build/packages/squall/**/build
2424+server/build/packages/squall/**/erl_crash.dump
2525+2626+# flyctl launch added from shared/.gitignore
2727+shared/**/*.beam
2828+shared/**/*.ez
2929+shared/build
3030+shared/**/erl_crash.dump
3131+fly.toml
+18
.github/workflows/fly-deploy.yml
···11+# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
22+33+name: Fly Deploy
44+on:
55+ push:
66+ branches:
77+ - main
88+jobs:
99+ deploy:
1010+ name: Deploy app
1111+ runs-on: ubuntu-latest
1212+ concurrency: deploy-group # optional: ensure only one action runs at a time
1313+ steps:
1414+ - uses: actions/checkout@v4
1515+ - uses: superfly/flyctl-actions/setup-flyctl@master
1616+ - run: flyctl deploy --remote-only
1717+ env:
1818+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
+58
Dockerfile
···11+ARG GLEAM_VERSION=v1.13.0
22+33+# Build stage - compile the application
44+FROM ghcr.io/gleam-lang/gleam:${GLEAM_VERSION}-erlang-alpine AS builder
55+66+# Install build dependencies
77+RUN apk add --no-cache \
88+ git \
99+ nodejs \
1010+ npm \
1111+ build-base \
1212+ sqlite-dev
1313+1414+# Configure git for non-interactive use
1515+ENV GIT_TERMINAL_PROMPT=0
1616+1717+# Add project code
1818+COPY ./shared /build/shared
1919+COPY ./client /build/client
2020+COPY ./server /build/server
2121+2222+# Install dependencies for all projects
2323+RUN cd /build/shared && gleam deps download
2424+RUN cd /build/client && gleam deps download
2525+RUN cd /build/server && gleam deps download
2626+2727+# Install JavaScript dependencies for client
2828+RUN cd /build/client && npm install
2929+3030+# Compile the client code and output to server's static directory
3131+RUN cd /build/client \
3232+ && gleam add --dev lustre_dev_tools \
3333+ && gleam run -m lustre/dev build client --minify --outdir=/build/server/priv/static
3434+3535+# Compile the server code
3636+RUN cd /build/server \
3737+ && gleam export erlang-shipment
3838+3939+# Runtime stage - slim image with only what's needed to run
4040+FROM ghcr.io/gleam-lang/gleam:${GLEAM_VERSION}-erlang-alpine
4141+4242+# Copy the compiled server code from the builder stage
4343+COPY --from=builder /build/server/build/erlang-shipment /app
4444+4545+# Set up the entrypoint
4646+WORKDIR /app
4747+RUN echo -e '#!/bin/sh\nexec ./entrypoint.sh "$@"' > ./start.sh \
4848+ && chmod +x ./start.sh
4949+5050+# Set environment variables
5151+ENV HOST=0.0.0.0
5252+ENV PORT=8080
5353+5454+# Expose the port the server will run on
5555+EXPOSE $PORT
5656+5757+# Run the server
5858+CMD ["./start.sh", "run"]
+22
fly.toml
···11+# fly.toml app configuration file generated for atconf on 2025-10-24T14:04:15-07:00
22+#
33+# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
44+#
55+66+app = 'atconf'
77+primary_region = 'sjc'
88+99+[build]
1010+1111+[http_service]
1212+ internal_port = 8080
1313+ force_https = true
1414+ auto_stop_machines = 'stop'
1515+ auto_start_machines = true
1616+ min_machines_running = 0
1717+ processes = ['app']
1818+1919+[[vm]]
2020+ memory = '512mb'
2121+ cpu_kind = 'shared'
2222+ cpus = 1
+8
server/.env.example
···33# Or the server will generate one on first run and print it to console
44SECRET_KEY_BASE=CHANGE_ME_TO_A_RANDOM_64_CHARACTER_STRING
5566+# Server Configuration
77+# Host address to bind to (defaults to 127.0.0.1 if not set)
88+# Use 0.0.0.0 to listen on all interfaces (required for Docker/production)
99+HOST=127.0.0.1
1010+1111+# Port the server will listen on (defaults to 3000 if not set)
1212+PORT=3000
1313+614# OAuth Configuration
715# These values will be used if environment variables are not set
816
+22-1
server/src/server.gleam
···99import gleam/http.{Get, Post}
1010import gleam/http/request
1111import gleam/httpc
1212+import gleam/int
1213import gleam/io
1314import gleam/json
1415import gleam/list
···8283 use db <- sqlight.with_connection("./sessions.db")
8384 let assert Ok(_) = session.init_db(db)
84858686+ // Get host from environment or default to 127.0.0.1
8787+ let host = case envoy.get("HOST") {
8888+ Ok(h) -> h
8989+ Error(_) -> "127.0.0.1"
9090+ }
9191+9292+ // Get port from environment or default to 3000
9393+ let port = case envoy.get("PORT") {
9494+ Ok(port_str) -> {
9595+ case int.parse(port_str) {
9696+ Ok(p) -> p
9797+ Error(_) -> 3000
9898+ }
9999+ }
100100+ Error(_) -> 3000
101101+ }
102102+103103+ io.println("Listening on http://" <> host <> ":" <> int.to_string(port))
104104+85105 let assert Ok(_) =
86106 handle_request(static_directory, db, oauth_config, _)
87107 |> wisp_mist.handler(secret_key_base)
88108 |> mist.new
8989- |> mist.port(3000)
109109+ |> mist.bind(host)
110110+ |> mist.port(port)
90111 |> mist.start
9111292113 process.sleep_forever()