Install
$ agentstack add skill-kennguyen887-agent-foundation-containerize-and-ship-a-service ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Containerize & ship a service
How a backend service is built into an image and shipped through CI/CD. Examples are Docker + GitLab CI with a Node/pnpm service; the principles port to any stack/CI. principle → ▸ Example → ▸ Other stacks. Branch→release flow itself is git-flow; DB migration rules are database-migrations; release backward-compat is release-safety — this skill is the build + pipeline mechanics.
Core principle
A small, reproducible image with NO secrets baked in, shipped by a thin per-repo pipeline that includes one shared template, gated by branch. Build creds live only in a throwaway build stage; runtime secrets come from the environment at deploy; the pipeline is maintained once, not per service.
1. Multi-stage build — heavy builder → slim runner
Compile in a builder stage; copy only the build output + production deps into a clean runtime stage, so toolchains/dev-deps never ship.
FROM /node:22-alpine AS builder
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@ --activate
COPY package.json pnpm-lock.yaml ./ # manifest first (cache) — see §4
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build && pnpm store prune
FROM /node:22-alpine AS runner # clean runtime base
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
COPY --from=builder /app/dist ./ # only the build output
RUN corepack enable && pnpm install --prod --frozen-lockfile && pnpm store prune
EXPOSE 3000
CMD ["node", "main.js"]
▸ Other stacks: go build in a builder → scratch/distroless runner; a JVM build → a JRE-only runtime image; Python wheels built then copied. Principle: build heavy, run slim.
2. Base images through a dependency proxy / private registry
Pull bases via a dependency proxy (or your private registry mirror), not Docker Hub directly — avoids rate limits and pins your supply chain. Pin exact versions (node:22-alpine3.22), not latest.
ARG CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX
FROM ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:22-alpine3.22 AS builder
▸ Other stacks: an ECR/Artifact Registry pull-through cache; a Harbor/Nexus proxy. Principle: a controlled, pinned base source.
3. Private deps during build — inject, then SCRUB before the final stage
Installing private packages needs a credential — pass it as a build ARG, use it, then delete it in the same stage, and rely on multi-stage so it never reaches the runtime image. Two common forms:
# (a) registry token
ARG CI_JOB_TOKEN
RUN echo "//gitlab.com/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc
RUN pnpm install --frozen-lockfile && rm -f .npmrc # scrub
# (b) SSH key for git+ssh deps
COPY id_rsa /root/.ssh/id_rsa
RUN chmod 600 /root/.ssh/id_rsa && ssh-keyscan gitlab.com > /root/.ssh/known_hosts \
&& git config --global url."git@gitlab.com:".insteadOf "https://gitlab.com/"
RUN pnpm install --frozen-lockfile && rm -f /root/.ssh/id_rsa # scrub
Never COPY a secret into the runtime stage, and never bake one into an ENV/layer — docker history exposes it. ▸ Other stacks: BuildKit --mount=type=secret (best — never lands in a layer at all), or a build-only stage. Principle: credentials are build-time only and disposable.
4. Layer caching — manifest first
Copy the dependency manifest + lockfile first and install, then copy source — so the (slow) install layer is cached and only rebuilds when deps change. Always install from the lockfile (--frozen-lockfile / npm ci) for reproducibility. ▸ Other stacks: go.mod/go.sum then go mod download; pom.xml then mvn dependency:go-offline; requirements.txt/poetry.lock first. Same idea everywhere.
5. Purpose-specific images — one concern each
Don't overload one image. Common split:
- app — the long-running service:
CMD ["node", "main.js"]. - migration — a run-once Job, not a service:
CMD pnpm run dbm:run && pnpm run dbs:run(apply
migrations + seeds, then exit). Run it as a gated pre-deploy step (§8), never inside the app's start.
- test — dev deps + the test runner, used only in CI.
▸ Other stacks: a migration init-container/Job; a separate test image/target. Principle: build, test, migrate, and serve are different lifecycles — different images/targets.
6. The CI pipeline — stages
A typical backend pipeline, in order, failing fast: install → lint → test → build image → push → migrate → deploy.
- Tests hit real deps: declare Postgres/Redis as CI service containers (or a compose file) so
integration tests run against a real DB, not mocks.
- Cache the dependency store between runs; pass build output as artifacts to later stages.
- Tag the image with the commit SHA (and the semver tag on a release) so every deploy is traceable.
▸ Other stacks: GitHub Actions jobs, CircleCI workflows, Jenkins stages — same ordering + a service/sidecar DB for tests.
7. Keep each repo's pipeline thin — include a shared template
Each service's CI file is just variables + an include of one org-wide template, so the pipeline (stages, build, deploy) is written once and every service inherits fixes/upgrades.
# a service's .gitlab-ci.yml — the whole thing
variables:
SERVICE_NAME: "your-service"
NODE_OPTIONS: "--max_old_space_size=4096"
include:
- project: "/ci-templates"
ref: master
file: "/backend/gitlab-ci.yml"
▸ Other stacks: GitHub reusable workflows (uses: org/.github/.../x.yml@ref), CircleCI orbs, a Jenkins shared library. Principle: centralize the pipeline; per-repo config is a few variables.
8. Per-branch → environment + secrets
- Gate deploys by branch, mirroring
git-flow: merges todevelop→ staging, a release
tag / master → production (manual approval for prod). Run the migration Job before the app deploy; abort the deploy if it fails.
- Secrets come from CI variables (masked + protected, protected branches only) and are injected as
environment variables at deploy — never COPY'd or ARG'd into the image. App config follows the config/env rules (validated on boot). ▸ Other stacks: environment-scoped secrets (GitHub Environments, Vault, SSM) injected at runtime; branch/tag-filtered deploy jobs.
Verification
- No secrets / no toolchain in the runtime image:
docker history --no-trunc | grep -iE 'authtoken|id_rsa|\.npmrc|secret|password'→ empty;docker run --rm gcc --version→ "not found" (slim runner, no build toolchain); runtime image size << builder. - Pinned bases via proxy, lockfile installs:
grep -nE 'FROM .*:latest' Dockerfile→ empty (exact tags only);grep -n 'frozen-lockfile\|npm ci' Dockerfilepresent;FROMlines reference the dependency-proxy/registry prefix, notdocker.iodirectly. - Cache order + split images: in the Dockerfile the
COPYof the manifest+lockfile precedesCOPY . .; the app image'sCMDstarts only the server —grep -n 'dbm:run\|migration' Dockerfileshows migrations are a separate image/Job, never in app start. - Thin pipeline, gates before ship: the repo CI file is basically
variables:+ oneinclude:(grep -c 'include:' .gitlab-ci.yml= 1); the shared template runslint+testagainst real service-container deps (Postgres/Redis) beforebuild/deploy. - Branch-gated deploys, env-injected secrets: CI rules map
develop→staging and tag/master→prod, with the migration Job gated before the app deploy; runtime secrets are masked CI variables injected as env (thedocker historycheck above confirms none are baked into a layer).
Related
git-flow— the branch→release flow these deploy rules mirror.database-migrations— what the migration Job runs (additive, reversible, ordered before deploy).release-safety— backward-compat + rollout gating around a deploy.structure-a-backend-service(the app being built) · global Config & Environment Rules.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kennguyen887
- Source: kennguyen887/agent-foundation
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.