如何在 Hubfly space 部署 monorepo 中的單一應用並使用自訂 Dockerfile
How to deploy one app from a monorepo with your own Dockerfile
Hubfly space 的教學示範如何從 monorepo 指定單一應用資料夾建置並部署為獨立服務,亦可用儀錶板中的自訂 Dockerfile 取代儲存庫內的 Dockerfile。若服務需要讀取同級資料夾或根目錄檔案,須把 Repository directory 設為涵蓋所需檔案的共同上層目錄,並相應調整 Dockerfile 路徑。
Originally published on the Hubfly space blog.
By Octave · drafted with AI, tested on Hubfly space
TL;DR
- What you get: one service from a monorepo running on its own HTTPS URL, built from its subfolder, with an optional Dockerfile that lives in the dashboard instead of your repo.
- Who it's for: teams with
apps/api,services/*orpackages/*layouts, and anyone whose repo Dockerfile is written for local dev, not production.- Time: about 5 minutes of setup. The build itself takes 30–45 seconds for a small Go service.
- Cost: free on the Shared tier (360 hours a month included), or about $2.23 a month for an always-on 0.5 vCPU / 512 MB Dedicated container.
Your repo holds an API, a web app, a worker and a shared/ folder. Your host wants one app per repo. So you split the repo, or fight the build until it finds the right Dockerfile. Both waste an afternoon.
This guide shows the faster path on Hubfly space: point the build at one folder, replace a laptop-only Dockerfile without touching Git, and handle shared code in a sibling folder. Try it on Hubfly space as you read; the free tier is enough.
The examples use the public docker/awesome-compose repo, which is a real monorepo: dozens of apps, each in its own folder. Swap in your own repo URL and paths.
How the build sees your monorepo
Three settings decide what gets built. The build context is the only set of files a Dockerfile's COPY can see, so getting it right avoids "file not found" errors that look like Docker bugs.
| Setting | What it does | Example |
|---|---|---|
| Repository directory | The folder the builder treats as the project root. It becomes the build context, and a Dockerfile at its top is used automatically. |
nginx-golang/backend |
| Custom Dockerfile | Dockerfile text pasted in the dashboard. It replaces any Dockerfile in the repo for this container. | A clean two-stage build |
| Branch | Which branch to clone. The form suggests main; change it if your default is master. |
master |
Step 1: Create a project and a container
- In the dashboard, click Create Project, name it (for example
shop), keep Cell (Hubfly space Cell runs managed containers), pick a region and click Create Project. - Click Create Container and name it
shop-api. Other services in the project reach it by a private hostname built from this name; the Review step shows the full address.
Step 2: Point the build at one folder
- Under Deployment Source, choose Git repository.
- For a public repo, click Self-hosted Git and paste its HTTPS URL (
<your-repo-url>), for examplehttps://github.com/docker/awesome-compose. No token is needed for public repos. For private GitHub repos, use GitHub App instead; for private GitLab, Bitbucket, Gitea or Forgejo repos, add an access token withread_repositoryandread_apiscopes. - Click Connect Repository.
- Set Branch to the branch you deploy (
masterhere). - Set Repository directory to the app's folder (
<apps/your-service>): here,nginx-golang/backend.
Leave Automatically detect the build on. If the folder has a Dockerfile at its top, the builder uses it; otherwise it detects the stack from files like go.mod.
Step 3: Pick resources and start the build
- On Resources, pick Shared Tier to stay free, or Dedicated Tier for an always-on service. For a small API, 0.5 vCPU and 512 MB is plenty. Untick Auto Sleep if it must answer instantly at all times.
- Click Continue through Configuration and Storage (a stateless API needs no volume), then Start Build.
The build page streams every step. In the log, look for Using working directory: nginx-golang/backend. That confirms the build is scoped to your folder.
When the repo's Dockerfile fails: paste one instead
Many monorepo Dockerfiles are written for local dev containers, not production. This example's Dockerfile has a dev-only stage that ends with:
COPY --from=gloursdocker/docker / /
The build stops with:
Build failed: stage "gloursdocker/docker" not found for COPY --from
ERROR: hubcell build failed: exit status 1
Two things cause this. The builder reads COPY --from= as the name of a stage in the same file, so copying straight from an outside image fails. And it runs the stages in order, so this dev-only stage gets built even though the final image doesn't need it.
You don't need to edit the repo. Use a Custom Dockerfile:
- On the failed build page, click Edit & retry. Your settings come back pre-filled.
- Untick Automatically detect the build.
- Paste this into Custom Dockerfile:
FROM golang:1.22-alpine AS build
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /out/backend main.go
FROM alpine:3.20
COPY --from=build /out/backend /usr/local/bin/backend
EXPOSE 80
CMD ["/usr/local/bin/backend"]
- Fill Run command with the same command as your
CMD:/usr/local/bin/backend. The form requires a run command once auto-detect is off, but with a Dockerfile it's ignored. The log says so: "submitted install/setup/build/run phases are ignored because a Dockerfile was provided." - Continue to Review and click Start Build.
Use the Go version your go.mod needs and a current base image tag; the versions above are only an example.
The log now shows Using custom Dockerfile from build request. For this service, the build takes about 41 seconds, and the container is running about a minute after you click Start Build. Why paste instead of commit? You keep the repo's dev setup untouched for your teammates, and the production recipe sits next to the container it builds.
Step 4: Give it a public URL
- Open the container and go to Network.
- Click Add Domain, keep Auto Endpoint, and check the port. The dialog scans the running container and suggests the open one (
80here). - Click Create Subdomain. You get a generated address like
https://<generated-name>.eu1.hubfly.appwith TLS on and HTTP redirected to HTTPS.
Open it to see the app. Logs shows the startup line (Go backend started! here).
When the service needs files from a sibling folder
If backend needs ../shared or a root-level file, setting Repository directory to backend won't work: anything outside that folder is not in the build context.
The fix is to set Repository directory to the closest folder that contains everything, and write paths relative to it:
# Repository directory: nginx-golang
FROM golang:1.22-alpine AS build
WORKDIR /src
ENV CGO_ENABLED=0
COPY backend/go.mod backend/go.sum ./
RUN go mod download
COPY backend/ .
RUN go build -o /out/backend main.go
FROM alpine:3.20
COPY --from=build /out/backend /usr/local/bin/backend
COPY README.md /usr/share/doc/shop/README.md
EXPOSE 80
CMD ["/usr/local/bin/backend"]
README.md sits outside the service's own folder and copies fine. This build takes about 32 seconds once base images are cached. Keep the context as narrow as you can: fewer files to send to the builder, and fewer chances that a broad COPY . . pulls something private into the image.
What it costs
Each always-on service here uses 0.5 vCPU, 512 MB of RAM and 4 GB of disk. At Hubfly space's rates (730 hours a month):
- CPU: 0.5 × $0.004 × 730 = $1.46
- RAM: 0.5 GB × $0.002 × 730 = $0.73
- Disk: 4 GB × $0.00001536 × 730 = $0.04
- Total: about $2.23 a month per service, the same figure the dashboard's cost panel shows. Two services from the same repo: about $4.47.
Builds are free. You pay for the running container. On the Shared tier (0.3 vCPU, 256 MB), a small service like this runs free within the 360 included hours and can sleep when idle.
Limits to know
-
No separate "Dockerfile path" field in the dashboard. The builder uses the
Dockerfileat the top of the Repository directory, or your Custom Dockerfile. If your Dockerfile lives elsewhere (saydocker/api.Dockerfile), paste its contents into Custom Dockerfile and fix theCOPYpaths for your chosen directory. -
COPY --fromexpects a stage name. A line that copies straight from an outside image fails withstage ... not found. Build what you need in a stage of your own, or leave dev tooling out of the production Dockerfile. - Dev-only stages still run. Keep them out of the Dockerfile you deploy, for example by pasting a production-only version into Custom Dockerfile.
-
The Custom Dockerfile lives in the dashboard, not in Git. It isn't code-reviewed or versioned with your repo. For a team, keep a copy in the repo (for example
deploy/api.Dockerfile) and paste from there.
What can go wrong
-
The wrong folder or branch gets built. The Review step doesn't show the Repository directory, and the Branch field suggests
main. Check both on the Source step before you click Start Build, especially if your default branch ismasterortrunk. -
stage "..." not found for COPY --from. Your Dockerfile copies from an outside image. Use a Custom Dockerfile without that line. -
A
COPYline can't find a file. The file is outside the Repository directory. Move the directory up to the common parent and prefix paths, as in the sibling-folder example. -
502 after a successful build. The app is listening on
127.0.0.1or a different port than the endpoint. Bind to0.0.0.0and match the port; see why Docker apps return 502.
Who this is not for
If every service in your repo needs the same shared build (for example a Turborepo turbo prune step that must run from the repo root), you'll write the Custom Dockerfile against the repo root, and build context gets large. That works, but a CI job that builds images and deploys them by image tag may suit you better. On Hubfly space you can redeploy a CI-built image with a deploy webhook.
Takeaway
One field picks the folder, one replaces a Dockerfile that only works locally, and moving up one folder solves shared code. No repo split needed.
Go further on Hubfly space
-
Run several services from one file: deploy a docker-compose.yml as-is, including
build:subfolders. - Add a database next to your API: a managed PostgreSQL database for about $2 a month.
- Coming from Railway? Move your app step by step.
Start free, 360 hours included. Create your first container on Hubfly space.
來源:DEV Community · dev.to



