# AGENTS.md

## Web Application Development & Deployment Standard

This file uses the existing application product, UI, security, data, testing, and quality standards from `/srv/downloads/AGENTS.md`, with deployment standardized on Docker Compose and Traefik.

Agents MUST read and apply Sections A through A.26 from `/srv/downloads/AGENTS.md` before using the deployment requirements below.

---

# 1. Supported Technology Stack

Unless explicitly specified otherwise by the user, use one of:

- ASP.NET Core Razor Pages
- Node.js Express.js

Choose the most suitable stack based on the application requirements.

All production deployments MUST use:

- Docker Engine
- Docker Compose v2
- Traefik as the reverse proxy
- A shared external Docker network named `traefik`
- Docker health checks
- Traefik-managed HTTPS certificates
- Container restart policies

Do not use Nginx, Certbot, Supervisor, PM2, or direct host-process deployment unless the user explicitly requests an exception.

---

# 2. Application Directory Structure

All applications MUST be stored under:

```text
/home/<appname>
```

Recommended structure:

```text
/home/<appname>/
├── src/
├── Dockerfile
├── compose.yml
├── .dockerignore
├── .env
├── .env.example
├── data/
├── uploads/
├── logs/
└── README.md
```

The application name must:

- Use lowercase letters, numbers, and hyphens only
- Be suitable as a Docker Compose project name
- Match the final subdomain exactly

Example:

```text
appname: crm
subdomain: crm.sentralogic.id
directory: /home/crm
```

---

# 3. Docker and Traefik Prerequisites

Verify Docker:

```bash
docker --version
docker compose version
docker info
```

Verify the shared network:

```bash
docker network inspect traefik
```

Create it when absent:

```bash
docker network create traefik
```

Inspect Traefik:

```bash
docker ps --filter name=traefik
docker inspect traefik
docker logs --tail 100 traefik
```

Traefik must:

- Be attached to the external `traefik` network
- Listen on ports 80 and 443
- Enable the Docker provider
- Set `exposedByDefault=false`
- Use a persistent ACME certificate store
- Redirect HTTP to HTTPS
- Restart automatically
- Keep the dashboard disabled or securely authenticated

Do not modify a working shared Traefik deployment before inspecting its existing configuration, certificate resolver, networks, entry points, and routes.

---

# 4. Container Port Assignment

Applications MUST listen on a fixed internal container port.

Recommended defaults:

```text
ASP.NET Core: 8080
Node.js:      3000
```

Container ports do not need to be unique across applications.

Prefer:

```yaml
expose:
  - "8080"
```

Do not publish application ports to the host unless direct host access is explicitly required.

Avoid:

```yaml
ports:
  - "5001:8080"
```

Traefik must route to the application through the shared Docker network.

Set the service port explicitly:

```yaml
- "traefik.http.services.crm.loadbalancer.server.port=8080"
```

The application must listen on `0.0.0.0` inside the container, not only `127.0.0.1`.

---

# 5. Required Health Endpoint

Every application MUST provide:

```text
/health
```

Expected response:

```json
{
  "status": "ok"
}
```

Requirements:

- Return HTTP 200 when ready
- Remain unauthenticated
- Avoid exposing secrets or detailed infrastructure data
- Use a lightweight implementation
- Apply reasonable timeouts

Every application container MUST define a health check.

Verify:

```bash
docker inspect --format='{{json .State.Health}}' <container-name>
```

Deployment is incomplete until the container becomes healthy.

---

# 6. Dockerfile Standards

Every application MUST include a production-ready Dockerfile.

Requirements:

- Use a maintained runtime image
- Use multi-stage builds when compilation is required
- Run as a non-root user where practical
- Copy dependency manifests before source code
- Install only runtime dependencies in the final image
- Define the correct working directory
- Do not copy `.env`, secrets, `.git`, or development artifacts
- Define the internal application port
- Use JSON-array `ENTRYPOINT` or `CMD`
- Keep runtime images minimal
- Do not embed credentials
- Rebuild after source or dependency changes

Required `.dockerignore` concepts:

```text
.git
.gitignore
.env
.env.*
!.env.example
node_modules
bin
obj
publish
coverage
.vscode
.idea
*.log
```

Adapt exclusions to the project so required build files are not omitted.

---

# 7. ASP.NET Core Docker Deployment

ASP.NET Core web projects MUST use:

```xml
<Project Sdk="Microsoft.NET.Sdk.Web">
```

Recommended Dockerfile:

```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY *.csproj ./
RUN dotnet restore

COPY . .
RUN dotnet publish -c Release -o /app/publish --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app

ENV ASPNETCORE_ENVIRONMENT=Production
ENV ASPNETCORE_URLS=http://+:8080

COPY --from=build /app/publish ./

USER app
EXPOSE 8080

ENTRYPOINT ["dotnet", "Application.dll"]
```

Adjust paths and assembly names to the actual project.

Use Release publish output only.

Configure forwarded headers safely so ASP.NET Core recognizes the original HTTPS scheme and client address behind Traefik.

---

# 8. Node.js Express Docker Deployment

Node.js applications MUST:

- Read the port from `PORT`
- Listen on `0.0.0.0`
- Provide `/health`
- Include a lockfile
- Use deterministic dependency installation
- Define a production start command

Example:

```js
const port = Number.parseInt(process.env.PORT || "3000", 10);

app.listen(port, "0.0.0.0", () => {
  console.log(`Application listening on ${port}`);
});
```

Recommended Dockerfile:

```dockerfile
FROM node:22-alpine AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-alpine AS runtime
WORKDIR /app

ENV NODE_ENV=production
ENV PORT=3000

COPY --from=dependencies /app/node_modules ./node_modules
COPY . .

RUN npm prune --omit=dev \
    && chown -R node:node /app

USER node
EXPOSE 3000

CMD ["npm", "start"]
```

Use a separate build stage when frontend or TypeScript compilation is required.

Review:

```bash
npm audit --omit=dev
```

Resolve compatible security warnings before production deployment.

---

# 9. Docker Compose Standard

Each application MUST include `compose.yml`.

Baseline example:

```yaml
name: crm

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file:
      - .env
    expose:
      - "8080"
    networks:
      - internal
      - traefik
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/health"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.crm.rule=Host(`crm.sentralogic.id`)"
      - "traefik.http.routers.crm.entrypoints=websecure"
      - "traefik.http.routers.crm.tls=true"
      - "traefik.http.routers.crm.tls.certresolver=letsencrypt"
      - "traefik.http.services.crm.loadbalancer.server.port=8080"
      - "traefik.http.services.crm.loadbalancer.healthcheck.path=/health"
      - "traefik.http.services.crm.loadbalancer.healthcheck.interval=30s"
      - "traefik.http.services.crm.loadbalancer.healthcheck.timeout=5s"

networks:
  internal:
    driver: bridge
    internal: true
  traefik:
    external: true
    name: traefik
```

Rules:

- Use unique router, service, and middleware names
- Set `traefik.enable=true` explicitly
- Set `traefik.docker.network=traefik`
- Attach only public-facing services to the Traefik network
- Keep databases and internal dependencies off the Traefik network
- Use `restart: unless-stopped` unless another policy is justified
- Do not use `network_mode: host`
- Do not publish database ports publicly
- Avoid `container_name` when scaling may be required
- Do not put secrets in Traefik labels
- Add resource limits where appropriate

Validate before deployment:

```bash
docker compose config
```

Never deploy when Compose validation fails.

---

# 10. Databases and Persistent Storage

Database containers MUST:

- Use named volumes or managed bind mounts
- Remain on an internal Docker network
- Avoid public host-port exposure
- Use health checks
- Use protected credentials
- Use a supported image version
- Use application-specific database users
- Avoid root database credentials in the application
- Have backup and restoration procedures
- Apply migrations in a controlled and idempotent way

Example:

```yaml
services:
  db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: "${DB_NAME}"
      MYSQL_USER: "${DB_USER}"
      MYSQL_PASSWORD: "${DB_PASSWORD}"
      MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD --silent"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

volumes:
  db-data:
```

`depends_on` alone does not prove application readiness. Implement resilient connection retry behavior or health-aware startup.

Back up persistent data before destructive image, schema, or volume changes.

---

# 11. Secrets and Environment Files

Store production configuration outside the image in:

```text
/home/<appname>/.env
```

Requirements:

- Set permissions to `600`
- Never commit `.env`
- Provide a safe `.env.example`
- Generate strong random secrets
- Separate development and production values
- Rotate exposed credentials
- Never print secrets in logs or final responses
- Do not put secrets in Traefik labels
- Prefer Docker secrets when supported and practical

Commands:

```bash
chmod 600 /home/<appname>/.env
docker compose config --quiet
```

Do not expose rendered environment values from `docker compose config` in public logs.

---

# 12. Traefik Routing Standard

Each public application MUST use:

```text
<appname>.sentralogic.id
```

Minimum labels:

```yaml
labels:
  - "traefik.enable=true"
  - "traefik.docker.network=traefik"
  - "traefik.http.routers.<appname>.rule=Host(`<appname>.sentralogic.id`)"
  - "traefik.http.routers.<appname>.entrypoints=websecure"
  - "traefik.http.routers.<appname>.tls=true"
  - "traefik.http.routers.<appname>.tls.certresolver=<resolver-name>"
  - "traefik.http.services.<appname>.loadbalancer.server.port=<container-port>"
```

Inspect the running Traefik instance and use its actual certificate resolver name. Do not assume it is `letsencrypt`.

Traefik normally handles WebSocket and Server-Sent Events forwarding automatically. Add timeouts only when long-lived workflows require them.

For large uploads, retain application-level file validation and size limits. Add Traefik buffering only when needed.

Dedicated subdomains are preferred over path-prefix routing.

Do not expose the application through both Traefik and an unauthenticated public host port.

---

# 13. DNS Verification

Before certificate issuance, verify:

```text
<appname>.sentralogic.id
```

resolves to the server public IP.

Commands:

```bash
dig +short <appname>.sentralogic.id
nslookup <appname>.sentralogic.id
getent ahosts <appname>.sentralogic.id
```

Verify IPv4 and IPv6 separately when both records exist.

Check:

- DNS propagation
- Public reachability of ports 80 and 443
- Firewall and security-group rules
- NAT or port forwarding
- Traefik logs
- Certificate resolver configuration
- ACME rate limits

---

# 14. Build and Deployment Workflow

Initial deployment:

```bash
cd /home/<appname>

docker compose config
docker compose build --pull
docker compose up -d
docker compose ps
```

Existing deployment update:

```bash
cd /home/<appname>

docker compose config
docker compose build --pull
docker compose up -d --remove-orphans
docker compose ps
```

Do not use:

```bash
docker compose down -v
```

during ordinary updates because it deletes named volumes.

After startup:

```bash
docker compose ps
docker compose logs --since 5m --no-color
docker inspect --format='{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}}' <container>
```

When migrations are required:

- Back up data first when risk exists
- Run migrations explicitly
- Record migration output
- Recreate or restart the application only when required
- Verify health afterward

---

# 15. Internal Validation

Validate from inside the container:

```bash
docker compose exec app sh -lc 'wget -qSO- http://127.0.0.1:8080/health'
```

When the image lacks an HTTP client, use a temporary diagnostic container on the internal network:

```bash
docker run --rm \
  --network <compose-project>_internal \
  curlimages/curl:latest \
  -i http://app:8080/health
```

Validate Traefik-network connectivity when required:

```bash
docker run --rm \
  --network traefik \
  curlimages/curl:latest \
  -i http://<service-name>:8080/health
```

Do not proceed to public validation when internal health checks fail.

---

# 16. Public HTTPS Validation

Required checks:

```bash
curl -I http://<appname>.sentralogic.id
curl -i https://<appname>.sentralogic.id
curl -i https://<appname>.sentralogic.id/health
```

Expected behavior:

- HTTP redirects to HTTPS
- TLS certificate is valid
- Homepage responds successfully
- `/health` returns HTTP 200
- No redirect loop occurs
- The application recognizes the original host and HTTPS scheme

Certificate inspection:

```bash
openssl s_client \
  -connect <appname>.sentralogic.id:443 \
  -servername <appname>.sentralogic.id \
  </dev/null
```

---

# 17. Traefik 404, 502, and 504 Troubleshooting

Do not declare deployment successful when the public URL returns 404, 502, or 504.

## Traefik 404

Check:

```bash
docker compose config
docker inspect <app-container>
docker logs --tail 200 traefik
```

Common causes:

- `traefik.enable=true` is missing
- Wrong hostname rule
- Router name conflict
- Application is not attached to the Traefik network
- Wrong network selected by Traefik
- Malformed labels
- Docker provider cannot access Docker
- DNS points to another server

## Traefik 502

Check:

```bash
docker compose ps
docker compose logs --tail 200 app
docker inspect --format='{{json .State.Health}}' <app-container>
docker network inspect traefik
docker logs --tail 200 traefik
```

Common causes:

- Application is not listening
- Wrong internal service port
- Application listens only on `127.0.0.1` inside the container
- Unhealthy or crashed container
- Traefik and application do not share a network
- Database failure prevented startup

## Traefik 504

Check:

- Slow or blocked application requests
- Database timeouts
- External dependency timeouts
- Deadlocks
- Incorrect application or proxy timeouts
- Resource exhaustion
- Unhandled asynchronous work

Fix the root cause, rebuild or recreate the affected service, and repeat all validation steps.

---

# 18. Logging and Fresh-Log Validation

Use container logs by default:

```bash
docker compose logs --tail 200 --no-color
docker compose logs --since 5m --no-color app
docker logs --since 5m traefik
```

Configure log rotation:

```yaml
logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "5"
```

Before smoke testing:

1. Record container ID, start time, health status, and restart count.
2. Run smoke tests.
3. Check the same values afterward.
4. Inspect only fresh logs from the test window.
5. Confirm no unexpected restart occurred.

Command:

```bash
docker inspect \
  --format='ID={{.Id}} Started={{.State.StartedAt}} Restarts={{.RestartCount}} Status={{.State.Status}} Health={{if .State.Health}}{{.State.Health.Status}}{{end}}' \
  <container>
```

Do not delete production logs to simplify inspection.

---

# 19. Container Security Baseline

Apply suitable hardening controls:

```yaml
security_opt:
  - no-new-privileges:true
cap_drop:
  - ALL
read_only: true
tmpfs:
  - /tmp
```

Apply only after confirming application compatibility.

Additional requirements:

- Run as non-root
- Do not mount `/var/run/docker.sock` into application containers
- Do not use `privileged: true`
- Avoid host filesystem mounts
- Mount writable directories explicitly
- Keep databases on internal networks
- Pin image major versions
- Scan images when tools are available
- Remove build tools from runtime images
- Disable production debugging endpoints
- Set resource limits where appropriate
- Do not store secrets in image layers
- Rebuild images regularly for security updates

Traefik is the only service that may require Docker-provider access. Prefer a restricted Docker socket proxy when practical.

---

# 20. Backup and Rollback

Before risky deployment:

- Back up databases
- Back up uploads and persistent files
- Preserve the current image tag or digest
- Preserve current Compose and environment configuration
- Record the running image ID
- Verify backups are readable

Recommended image tags:

```text
<appname>:2026-07-21-1700
<appname>:previous
<appname>:current
```

Do not remove volumes during rollback.

After rollback, repeat health, logs, Traefik, and HTTPS validation.

---

# 21. Required End-to-End Smoke Test

For authenticated applications:

1. Verify the application container is healthy.
2. Load the HTTPS login page.
3. Confirm styles, scripts, icons, and CSRF values load.
4. Log in with approved test credentials.
5. Confirm the dashboard loads.
6. Open each primary module.
7. Test server-side pagination, sorting, and filters.
8. Test at least one create and edit operation.
9. Test file upload when supported.
10. Test one foreign-key lookup workflow.
11. Confirm unauthenticated API requests return HTTP 401 JSON.
12. Confirm unauthorized API requests return HTTP 403 JSON.
13. Inspect fresh application and Traefik logs.
14. Confirm container ID, health, and restart count remain stable.
15. Remove disposable test records safely.

After frontend changes, use asset hashing or asset version updates to prevent stale client files.

---

# 22. Post-Deployment Validation

Deployment is complete only when all checks pass:

- Source exists under `/home/<appname>`
- Dockerfile exists
- `.dockerignore` exists
- `compose.yml` exists
- `.env` is protected and not committed
- Docker build succeeds
- Compose validation succeeds
- Application container is running and healthy
- Restart count remains stable
- Internal `/health` returns HTTP 200
- Application and Traefik share the expected network
- Router and service labels are correct
- DNS resolves to the server
- HTTP redirects to HTTPS
- TLS certificate is valid
- HTTPS homepage responds
- HTTPS `/health` returns HTTP 200
- Fresh application logs contain no deployment errors
- Fresh Traefik logs contain no routing or certificate errors
- No Traefik 404, 502, or 504 remains
- Required authenticated smoke tests pass

Recommended commands:

```bash
cd /home/<appname>
docker compose config
docker compose ps
docker compose logs --since 5m --no-color
docker inspect <app-container>
docker network inspect traefik
docker logs --since 5m traefik
curl -I http://<appname>.sentralogic.id
curl -i https://<appname>.sentralogic.id
curl -i https://<appname>.sentralogic.id/health
```

---

# 23. Deployment Success Checklist

```text
[ ] App name is lowercase and subdomain-safe
[ ] App source exists under /home/<appname>
[ ] Correct application stack selected
[ ] Dockerfile created
[ ] Multi-stage build used when appropriate
[ ] Runtime container runs as non-root where practical
[ ] .dockerignore created
[ ] compose.yml created
[ ] docker compose config passed
[ ] Production secrets stored outside the image
[ ] .env permissions restricted
[ ] Database is not publicly exposed
[ ] Persistent volumes configured
[ ] Backup completed before risky changes
[ ] Shared Traefik network exists
[ ] App is attached to the Traefik network
[ ] traefik.enable=true is set
[ ] Router hostname is correct
[ ] Router entry point is websecure
[ ] TLS is enabled
[ ] Correct certificate resolver is configured
[ ] Traefik service port matches the container port
[ ] Health endpoint implemented
[ ] Docker health check implemented
[ ] Image build succeeded
[ ] Containers started successfully
[ ] Application container is healthy
[ ] Restart count is stable
[ ] Internal health endpoint returns HTTP 200
[ ] DNS resolves to the server
[ ] HTTP redirects to HTTPS
[ ] HTTPS homepage responds
[ ] HTTPS /health returns HTTP 200
[ ] Certificate is valid
[ ] Fresh application logs checked
[ ] Fresh Traefik logs checked
[ ] No 404/502/504 remains
[ ] Authenticated smoke test completed when applicable
[ ] Final public URL confirmed accessible
```

If any required item fails, deployment is not finished.

---

# 24. Agent Responsibilities

When developing and deploying a web application:

1. Read and apply Sections A through A.26 from `/srv/downloads/AGENTS.md`.
2. Create the application under `/home/<appname>`.
3. Build the complete application.
4. Add `/health`.
5. Create a production Dockerfile.
6. Create `.dockerignore`.
7. Create `compose.yml`.
8. Configure environment values securely.
9. Inspect the existing Traefik deployment.
10. Verify or create the shared `traefik` network.
11. Configure unique Traefik router, service, and middleware labels.
12. Validate Compose configuration.
13. Build the image.
14. Start or update containers.
15. Verify container health.
16. Verify internal connectivity.
17. Verify DNS.
18. Verify HTTP-to-HTTPS redirection.
19. Verify HTTPS homepage and `/health`.
20. Run required smoke tests.
21. Inspect fresh application and Traefik logs.
22. Verify restart count and process stability.
23. Confirm the application is accessible at:

    ```text
    https://<appname>.sentralogic.id
    ```

The task is not complete until the application is accessible through Traefik over HTTPS and all required checks pass.

---

# 25. Existing Deployment Migration Rule

When converting an existing host-process, Supervisor, Nginx, or Certbot deployment:

1. Inspect the current process, environment, ports, data paths, uploads, logs, DNS, and certificate behavior.
2. Back up application data and configuration.
3. Build and test the container without stopping the current application where possible.
4. Attach the new service to Traefik with a temporary hostname or controlled router priority when practical.
5. Verify health and functional behavior.
6. Cut traffic over to Traefik.
7. Confirm HTTPS and application behavior.
8. Stop and disable the old Supervisor or systemd application service.
9. Remove the obsolete Nginx site only after successful cutover.
10. Preserve old data, certificates, and configuration until rollback is no longer required.
11. Confirm ports 80 and 443 are owned only by the intended Traefik instance.
12. Repeat the complete post-deployment checklist.

Never stop a working production deployment before a validated replacement path exists unless unavoidable and explicitly approved.
