Skip to content

Instantly share code, notes, and snippets.

@Nezteb
Last active April 4, 2025 21:37
Show Gist options
  • Save Nezteb/e05aa6585daaa05f853ee8f42896ad14 to your computer and use it in GitHub Desktop.
Save Nezteb/e05aa6585daaa05f853ee8f42896ad14 to your computer and use it in GitHub Desktop.
Deploying Fossil with TLS on Fly.io via Docker

Deploying Fossil SCM on Fly.io

  1. Read about Fossil and figure out if you want to try it as an alternative to git!
  2. Follow Fly.io's hands-on guide to create an account and your first app.
    • fly init
  3. Allocate an IPv4 address for your app.
    • fly ips allocate-v4
  4. Create a volume to use:
    • fly volumes create name_of_your_volume
  5. Copy the Dockerfile and fly.toml files in this gist.
  6. Replace the REDACTED_ bits of fly.toml with the actual values.
    • REDACTED_APP: Created during the fly init step.
    • REDACTED_REGION: Set during the fly init step.
    • REDACTED_VOLUME: Creatd during the fly volumes create step.
  7. Deploy!
    • fly deploy
  8. Observe the deploy logs for your app from the "Monitoring" tab. While the app is deployed, you'll see a log that contains your defualt admin password that you should save in your password manager: (you will change it later)
    • admin-user: admin (initial password is "abcdefghij")
  9. Optional: set up your Fly.io custom domain and SSL/TLS certificates.
  10. Log into your Fossil web UI and go to the "Admin" tab to set up your new instance!

Congrats, you now have a working Fossil server!

# Pulled from https://fossil-scm.org/home/file?name=Dockerfile&ci=trunk
# With more build ARGs, cleaner tarball extraction, HTTPS, etc.
ARG ALPINE_VERSION
ARG FSLHSH
ARG FSLVER
ARG FSLCFG=""
# syntax=docker/dockerfile:1.3
# See www/containers.md for documentation on how to use this file.
## ---------------------------------------------------------------------
## STAGE 1: Build static Fossil binary
## ---------------------------------------------------------------------
### We aren't pinning to a more stable version of Alpine because we want
### to build with the latest tools and libraries available in case they
### fixed something that matters to us since the last build. Everything
### below depends on this layer, and so, alas, we toss this container's
### cache on Alpine's release schedule, roughly once a month.
FROM alpine:${ALPINE_VERSION} AS builder
WORKDIR /tmp
ARG FSLHSH
ARG FSLVER
ARG FSLCFG
### Bake the basic Alpine Linux into a base layer so it only changes
### when the upstream image is updated or we change the package set.
RUN set -x \
&& apk update \
&& apk upgrade --no-cache \
&& apk add --no-cache \
gcc make \
linux-headers musl-dev \
openssl-dev openssl-libs-static \
zlib-dev zlib-static curl
### Build Fossil as a separate layer so we don't have to rebuild the
### Alpine environment for each iteration of Fossil's dev cycle.
ARG FSLDIR="fossil-src-${FSLVER}"
ARG FSLSTB="${FSLDIR}.tar.gz"
ARG FSLURL="https://fossil-scm.org/home/tarball/${FSLHSH}/${FSLSTB}"
RUN curl -o $FSLSTB $FSLURL
RUN tar -xzf $FSLSTB
RUN mv $FSLDIR src
RUN m=src/src/main.mk && src/configure --static CFLAGS='-Os -s' $FSLCFG && make -j11
## ---------------------------------------------------------------------
## STAGE 2: Pare that back to the bare essentials.
## ---------------------------------------------------------------------
FROM busybox AS os
ARG UID=499
### Set up that base OS for our specific use without tying it to
### anything likely to change often. So long as the user leaves
### UID alone, this layer will be durable.
RUN set -x \
&& mkdir log museum \
&& echo "root:x:0:0:Admin:/:/false" > /tmp/passwd \
&& echo "root:x:0:root" > /tmp/group \
&& echo "fossil:x:${UID}:${UID}:User:/museum:/false" >> /tmp/passwd \
&& echo "fossil:x:${UID}:fossil" >> /tmp/group
## ---------------------------------------------------------------------
## STAGE 3: Drop BusyBox, too, now that we're done with its /bin/sh &c
## ---------------------------------------------------------------------
FROM scratch AS run
COPY --from=os /tmp/group /tmp/passwd /etc/
COPY --from=os --chown=fossil:fossil /log /log/
COPY --from=os --chown=fossil:fossil /museum /museum/
COPY --from=os --chmod=1777 /tmp /tmp/
COPY --from=builder /tmp/fossil /bin/
## ---------------------------------------------------------------------
## RUN!
## ---------------------------------------------------------------------
ENV PATH "/bin"
EXPOSE 8080/tcp
USER fossil
ENTRYPOINT [ "fossil", "server", "museum/repo.fossil" ]
CMD [ \
"--create", \
"--https", \
"--jsmode", "bundled", \
"--user", "admin" ]
app = "REDACTED_APP"
kill_signal = "SIGINT"
kill_timeout = 5
primary_region = "REDACTED_REGION"
processes = []
[build.args]
ALPINE_VERSION=3
FSLHSH="f9aa474081f0618c76f4c2f4d6f0277a3fd480aa185d7da0b8b61b00fad1aa78"
FSLVER="2.12"
[env]
[experimental]
auto_rollback = true
# Data
[[mounts]]
destination = "/museum"
source = "REDACTED_VOLUME"
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = false
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
@maphew
Copy link

maphew commented Apr 4, 2025

Thank you for this kickstart! I was able to get it working with less settings (which may or not be a good thing).

# Dockerfile for initializing Fossil machine on Fly.io
FROM alpine:latest
RUN apk add --no-cache fossil
WORKDIR /app

# Declare volume and expose port
VOLUME ["/persistent"]
EXPOSE 8080

# Create a separate startup script file
COPY <<EOF /app/startup.sh
#!/bin/sh
mkdir -p /persistent/repos

if [ ! -f /persistent/repos/*.fossil ]; then
  echo "No repositories found, initializing new Fossil repo..." > /persistent/fossil-init.log
  fossil version >> /persistent/fossil-init.log
  
  fossil new --project-name flyio-fossil \\
    --project-desc "Hosting Fossil SCM on Fly.io" \\
    /persistent/repos/flyio.fossil \\
    --admin-user CHANGE_ME \\
    >> /persistent/fossil-init.log
    
  echo "Repository initialization complete" >> /persistent/fossil-init.log
else
  echo "Fossil repository already exists" > /persistent/fossil-init.log
fi

echo "Starting Fossil server..."
exec fossil server /persistent/repos --repolist \\
  --port 8080 \\
  --jsmode bundled \\
  --baseurl https://\$FLY_APP_NAME.fly.dev
EOF

RUN chmod +x /app/startup.sh

# Use the startup script as the entry point
ENTRYPOINT ["/app/startup.sh"]
# fly.toml app configuration file generated for flyio-fossil on 2025-04-02T20:54:55-07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'CHANGE_ME'
primary_region = 'YOURS'

[build]
  dockerfile = 'Dockerfile'

[[mounts]]
  source = 'persistent'
  destination = '/persistent'

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = 'stop'
  auto_start_machines = true
  min_machines_running = 0
  processes = ['app']

[[vm]]
  size = 'shared-cpu-1x'

Initialized and published with:

fly launch --no-deploy --dockerfile Dockerfile --vm-memory 256 --generate-name --org personal --region YOURS
fly vol create persistent --region yyz --size 1 --count 2
fly deploy

Then replace the empty default repo with your own using fly sftp shell ... (or fly ssh console -C fossil new ...)

Expanded details of the recipe at https://flyio-fossil.fly.dev/flyio/file?name=Readme.md&ci=tip (I don't know how long I'll keep the demo going. So far it's free though so it will be some while anyway.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment